Skip to content

Commit d97e770

Browse files
committed
docs(alm): add listener tests to counter example
- Description Why: We have received questions about how to test listeners
1 parent c87f803 commit d97e770

File tree

2 files changed

+141
-1
lines changed

2 files changed

+141
-1
lines changed

examples/action-listener/counter/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"scripts": {
1818
"start": "react-scripts start",
1919
"build": "react-scripts build",
20-
"lint:ts": "tsc --noEmit"
20+
"lint:ts": "tsc --noEmit",
21+
"test": "react-scripts test --runInBand"
2122
},
2223
"eslintConfig": {
2324
"extends": [
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit'
2+
import { setupCounterListeners } from '../listeners'
3+
import { counterSlice, counterActions, counterSelectors } from '../slice'
4+
import type { AppStartListening } from '../../../store'
5+
6+
function delay(timerMs: number): Promise<number> {
7+
return new Promise((resolve) => {
8+
setTimeout(resolve, timerMs, timerMs)
9+
})
10+
}
11+
12+
jest.useRealTimers()
13+
14+
describe('counter - listeners', () => {
15+
const onMiddlewareError = jest.fn((): void => {}) // https://jestjs.io/docs/mock-function-api
16+
17+
/**
18+
* @see https://redux-toolkit.js.org/api/createListenerMiddleware
19+
*/
20+
const listenerMiddlewareInstance = createListenerMiddleware({
21+
onError: onMiddlewareError,
22+
})
23+
24+
function setupTestStore() {
25+
return configureStore({
26+
reducer: {
27+
[counterSlice.name]: counterSlice.reducer,
28+
},
29+
middleware: (gDM) => gDM().prepend(listenerMiddlewareInstance.middleware),
30+
})
31+
}
32+
33+
let store = setupTestStore()
34+
35+
beforeEach(() => {
36+
listenerMiddlewareInstance.clearListeners() // Stops and cancels active listeners https://redux-toolkit.js.org/api/createListenerMiddleware#clearlisteners
37+
onMiddlewareError.mockClear() // https://jestjs.io/docs/mock-function-api#mockfnmockclear
38+
store = setupTestStore() // resets store state
39+
40+
setupCounterListeners(
41+
listenerMiddlewareInstance.startListening as AppStartListening
42+
)
43+
})
44+
45+
describe('onUpdateAsync', () => {
46+
const delayMs = 10
47+
const initialValue = 2
48+
const delta = 2
49+
50+
it('asynchronously adds `payload.delta` after `payload.delayMs` to counter', async () => {
51+
store.dispatch(counterActions.addCounter({ initialValue }))
52+
53+
const { id } = counterSelectors.selectAll(store.getState())[0]
54+
55+
store.dispatch(counterActions.updateByAsync({ id, delayMs, delta }))
56+
57+
expect(counterSelectors.selectById(store.getState(), id)?.value).toBe(
58+
initialValue
59+
)
60+
61+
await delay(delayMs)
62+
63+
expect(counterSelectors.selectById(store.getState(), id)?.value).toBe(
64+
initialValue + delta
65+
)
66+
})
67+
68+
it('stops updates if cancelAsyncUpdates is dispatched', async () => {
69+
store.dispatch(counterActions.addCounter({ initialValue }))
70+
71+
const { id } = counterSelectors.selectAll(store.getState())[0]
72+
73+
store.dispatch(counterActions.updateByAsync({ id, delayMs, delta }))
74+
75+
expect(counterSelectors.selectById(store.getState(), id)?.value).toBe(
76+
initialValue
77+
)
78+
79+
store.dispatch(counterActions.cancelAsyncUpdates(id))
80+
81+
await delay(delayMs)
82+
83+
expect(counterSelectors.selectById(store.getState(), id)?.value).toBe(
84+
initialValue
85+
)
86+
})
87+
})
88+
89+
describe('onUpdateByPeriodically', () => {
90+
const intervalMs = 10
91+
const initialValue = 2
92+
const delta = 2
93+
94+
it('periodically adds `payload.delta` after `payload.intervalMs` to counter', async () => {
95+
store.dispatch(counterActions.addCounter({ initialValue }))
96+
97+
const { id } = counterSelectors.selectAll(store.getState())[0]
98+
99+
store.dispatch(
100+
counterActions.updateByPeriodically({ id, intervalMs, delta })
101+
)
102+
103+
for (let i = 0; i < 2; i++) {
104+
expect(counterSelectors.selectById(store.getState(), id)?.value).toBe(
105+
initialValue + i * delta
106+
)
107+
108+
await delay(intervalMs)
109+
110+
expect(counterSelectors.selectById(store.getState(), id)?.value).toBe(
111+
initialValue + (i + 1) * delta
112+
)
113+
}
114+
})
115+
116+
it('stops updates if cancelAsyncUpdates is dispatched', async () => {
117+
store.dispatch(counterActions.addCounter({ initialValue }))
118+
119+
const { id } = counterSelectors.selectAll(store.getState())[0]
120+
121+
store.dispatch(
122+
counterActions.updateByPeriodically({ id, intervalMs, delta })
123+
)
124+
125+
await delay(intervalMs)
126+
expect(counterSelectors.selectById(store.getState(), id)?.value).toBe(
127+
initialValue + delta
128+
)
129+
130+
store.dispatch(counterActions.cancelAsyncUpdates(id))
131+
132+
await delay(intervalMs)
133+
134+
expect(counterSelectors.selectById(store.getState(), id)?.value).toBe(
135+
initialValue + delta
136+
)
137+
})
138+
})
139+
})

0 commit comments

Comments
 (0)