Kea Test Utilities
Kea Test Utilities (kea-test-utils) is a library designed to streamline the testing of Kea logic stores, offering a fluent API for asserting logic behavior in JavaScript and TypeScript environments. Currently at version 0.2.4, it is actively maintained and ships with TypeScript types, aligning with the Kea 3.x ecosystem. The package provides core utilities such as `expectLogic` and `partial`, enabling developers to dispatch actions and then assert against the resulting state changes and subsequent dispatched actions. A critical aspect of its usage involves integrating the `testUtilsPlugin` with Kea's `resetContext` function before each test to ensure a clean, isolated testing environment. This allows for precise control over the Kea context, preventing state leakage and ensuring reliable test results. Its primary differentiator is its deep integration with Kea's internal mechanisms, providing comprehensive tools for both querying recorded action history and awaiting new actions for verification, making it indispensable for robust Kea application testing.
Common errors
-
Error: Plugin 'listenersPlugin' was loaded twice.
cause The `listenersPlugin` was explicitly added to `resetContext({ plugins: [...] })` after upgrading to Kea v2.x or v3.x, where listeners are now built-in to the core `kea` package.fixRemove `listenersPlugin` from the `plugins` array in your `resetContext` call. -
Error: `expectLogic` requires `testUtilsPlugin` to be enabled in `resetContext`.
cause The `testUtilsPlugin` was not included in the `plugins` array when `resetContext` was called, preventing `expectLogic` from initializing its recording mechanisms.fixEnsure `testUtilsPlugin` is passed to `resetContext` in your `beforeEach` hook: `resetContext({ plugins: [testUtilsPlugin] })`. -
TypeError: Cannot read properties of undefined (reading 'actions') when trying to call logic.actions.someAction
cause The Kea logic might not have been properly mounted or initialized within the test context, or `resetContext` was not called, leading to an undefined logic instance. This can also happen if `resetContext` is called *after* logic instantiation.fixVerify that `resetContext({ plugins: [testUtilsPlugin] })` is called within a `beforeEach` hook and that your logic is instantiated or connected correctly *after* the context has been reset.
Warnings
- breaking Upgrading Kea from v1.x to v2.x or v3.x changed how plugins are handled, specifically making 'listeners' built-in. If you explicitly passed `listenersPlugin` to `resetContext({ plugins: [...] })` in Kea v1.x, you must remove it when upgrading to Kea v2.x/v3.x or Kea will throw an error about the plugin being imported twice, preventing tests from running.
- gotcha Failure to call `resetContext({ plugins: [testUtilsPlugin] })` before each test can lead to global state leakage between tests, causing unreliable and inconsistent test results. Kea manages a global context that must be reset for proper test isolation.
- gotcha The `expectLogic` utility relies on the `testUtilsPlugin` being loaded into Kea's context. If `testUtilsPlugin` is not included in the `plugins` array when calling `resetContext`, `expectLogic` will not be able to record actions or access the logic's internal state history, leading to test failures or unexpected behavior.
- breaking In Kea 1.0, the default path for logic without an explicit path (or when not using the Babel plugin) changed from `kea.inline` to `kea.logic`. If your tests or application hardcoded `kea.inline` anywhere, this change will cause issues. While this change is in the core Kea library, it impacts how logic paths are resolved in test environments.
Install
-
npm install kea-test-utils -
yarn add kea-test-utils -
pnpm add kea-test-utils
Imports
- expectLogic
const { expectLogic } = require('kea-test-utils')import { expectLogic } from 'kea-test-utils' - partial
import partial from 'kea-test-utils/partial'
import { partial } from 'kea-test-utils' - testUtilsPlugin
import testUtilsPlugin from 'kea-test-utils/plugin'
import { testUtilsPlugin } from 'kea-test-utils' - resetContext
import { resetContext } from 'kea-test-utils'import { resetContext } from 'kea'
Quickstart
import { kea, resetContext } from 'kea';
import { expectLogic, testUtilsPlugin, partial } from 'kea-test-utils';
interface CounterLogicState {
count: number;
}
interface CounterLogicActions {
increment: (amount: number) => { amount: number };
decrement: (amount: number) => { amount: number };
}
const counterLogic = kea<CounterLogicState, CounterLogicActions>({
path: ['scenes', 'counter'],
actions: {
increment: (amount: number) => ({ amount }),
decrement: (amount: number) => ({ amount }),
},
reducers: {
count: [
0,
{
increment: (state, { amount }) => state + amount,
decrement: (state, { amount }) => state - amount,
},
],
},
});
describe('counterLogic', () => {
beforeEach(() => {
// Essential: Reset Kea's context and enable test utilities before each test
resetContext({ plugins: [testUtilsPlugin] });
});
test('should increment the count', async () => {
await expectLogic(counterLogic, () => {
// Dispatch actions directly on the logic
counterLogic.actions.increment(5);
})
.toDispatchActions(['increment'])
.toMatchValues({ count: 5 });
});
test('should decrement the count', async () => {
await expectLogic(counterLogic, () => {
counterLogic.actions.decrement(2);
})
.toDispatchActions(['decrement'])
.toMatchValues(partial({ count: 3 })); // Using partial to match only specific values
});
test('should handle multiple actions', async () => {
await expectLogic(counterLogic, () => {
counterLogic.actions.increment(10);
counterLogic.actions.decrement(3);
})
.toDispatchActions([
'increment',
'decrement'
])
.toMatchValues({ count: 7 });
});
});