Skip to content

Commit f39d28a

Browse files
committed
Update README and add slice mocking examples
1 parent b76b570 commit f39d28a

File tree

1 file changed

+107
-16
lines changed

1 file changed

+107
-16
lines changed

README.md

Lines changed: 107 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,39 @@
33
<p>A <a href="https://react.dev/">React</a> hook for working with <a href="https://redux-toolkit.js.org/">Redux Toolkit</a> slices, with zero setup and boilerplate ⚛️ 🛠️
44
</p>
55
<p>
6-
<a href="https://www.npmjs.com/package/use-rtk-slice"><img alt="npm" src="https://img.shields.io/npm/v/use-rtk-slice.svg"></a>
7-
<a href="https://www.npmjs.com/package/use-rtk-slice" target="_blank"><img alt="Downloads per month" src="https://img.shields.io/npm/dm/use-rtk-slice.svg" /></a>
8-
<a href="https://github.com/Lambdaphile/use-rtk-slice/blob/main/src/useSlice.test.tsx"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
9-
<a href="https://www.typescriptlang.org/"><img alt="TypeScript Ready" src="https://img.shields.io/badge/TypeScript-Ready-blue.svg"></a>
6+
<a href="https://www.npmjs.com/package/use-rtk-slice" target="_blank"><img alt="npm" src="https://img.shields.io/npm/v/use-rtk-slice.svg?label=NPM"></a>
7+
<a href="https://www.npmjs.com/package/use-rtk-slice" target="_blank"><img alt="Downloads per month" src="https://img.shields.io/npm/dm/use-rtk-slice.svg?label=Downloads" /></a>
8+
<a href="https://github.com/Lambdaphile/use-rtk-slice/blob/main/src/useSlice.test.tsx" target="_blank"><img alt="Coverage" src="https://img.shields.io/badge/Coverage-100%25-brightgreen"></a>
9+
<a href="https://www.typescriptlang.org/" target="_blank"><img alt="TypeScript Ready" src="https://img.shields.io/badge/TypeScript-Ready-blue.svg"></a>
1010
</p>
1111
<pre>npm i <a href="https://www.npmjs.com/package/use-rtk-slice">use-rtk-slice</a></pre>
1212
</div>
1313
<hr/>
1414

15-
Using Redux Toolkit slices with plain `useSelector` and `useDispatch` hooks requires:
15+
Using Redux Toolkit slices with plain `useSelector` and `useDispatch` hooks often requires:
1616

17-
1. Manually defining typed versions of `useSelector` and `useDispatch` in TypeScript projects - [Define `useAppSelector` and `useAppDispatch`](https://redux-toolkit.js.org/tutorials/typescript#define-typed-hooks).
17+
1. Manually defining typed versions of `useSelector` and `useDispatch` in TypeScript projects: [Define `useAppSelector` and `useAppDispatch`](https://redux-toolkit.js.org/tutorials/typescript#define-typed-hooks).
1818
2. Repeatedly writing `const dispatch = useDispatch()` just to dispatch an action.
1919
3. Slice selectors are not bound - using them requires passing the relevant slice state, e.g., `selector({ stateName: state })`.
2020

2121
The `useSlice` hook from `use-rtk-slice` handles all of this: it binds actions and selectors internally, and returns fully typed, ready-to-use slice state, actions, and selectors.
2222

23+
## Contents
24+
25+
- [Usage](#usage)
26+
- [Testing (Mocking Slices)](#testing-mocking-slices)
27+
- [Mock State](#mock-state)
28+
- [Mock Selectors](#mock-selectors)
29+
- [Mock and Spy on Actions](#mock-and-spy-on-actions)
30+
2331
## Usage
2432

2533
Define a RTK slice:
2634

2735
`todosSlice.ts`
2836

2937
```ts
30-
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
38+
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
3139

3240
interface Todo {
3341
id: number
@@ -38,12 +46,12 @@ interface Todo {
3846
const initialState: Todo[] = [
3947
{
4048
id: Date.now(),
41-
name: 'Feed the cat 🐱',
49+
name: '🧪 Write unit tests',
4250
done: false
4351
}
4452
]
4553

46-
export const todosSlice = createSlice({
54+
const todosSlice = createSlice({
4755
name: 'todos',
4856
initialState: initialState,
4957
reducers: {
@@ -54,19 +62,24 @@ export const todosSlice = createSlice({
5462
return state.map((todo) =>
5563
todo.id === action.payload ? { ...todo, done: !todo.done } : todo
5664
)
65+
},
66+
removeTodo(state, action: PayloadAction<Todo['id']>) {
67+
return state.filter((todo) => todo.id !== action.payload)
5768
}
5869
},
5970
selectors: {
60-
selectCompleted: (state) => state.filter((todo) => todo.done)
71+
selectCompleted(state) {
72+
return state.filter((todo) => todo.done)
73+
}
6174
}
6275
})
6376
```
6477

65-
Destructure the `state`, and the bound `actions` and `selectors` from the slice as needed using, the `useSlice` hook:
78+
Destructure the `state`, and the bound `actions` and `selectors` from the slice as needed, using the `useSlice` hook:
6679

6780
`TodoList.tsx`
6881

69-
```tsx
82+
```ts
7083
import useSlice from 'use-rtk-slice'
7184
import { todosSlice } from './todosSlice'
7285

@@ -84,15 +97,93 @@ function TodoList() {
8497
checked={done}
8598
onChange={() => actions.toggleTodo(id)}
8699
/>
87-
{name}
100+
{done ? <s>{name}</s> : name}
88101
</label>
102+
<button onClick={() => actions.removeTodo(id)}>🗑️</button>
89103
</li>
90104
))}
91105
</ul>
92-
<p>
93-
<strong>Completed Todos:</strong> {selectors.selectCompleted().length}
94-
</p>
106+
<p>Completed todo count: {selectors.selectCompleted().length}</p>
95107
</div>
96108
)
97109
}
98110
```
111+
112+
## Testing (Mocking Slices)
113+
114+
To mock slices, use the `mockSlice` utility from `use-rtk-slice/test/vitest` or `use-rtk-slice/test/jest`:
115+
116+
### Mock State
117+
118+
```ts
119+
import { mockSlice } from 'use-rtk-slice/test/vitest'
120+
// or
121+
import { mockSlice } from 'use-rtk-slice/test/jest'
122+
123+
import { App } from './App'
124+
import { todoSlice } from './todoSlice'
125+
126+
describe('TodoList', () => {
127+
beforeEach(() => {
128+
mockSlice.beforeEach()
129+
})
130+
131+
it('should render todos', () => {
132+
mockSlice(todosSlice, {
133+
state: [
134+
{ id: 0, name: '🧪 Write unit tests', done: false },
135+
{ id: 1, name: '📝 Update README', done: false }
136+
]
137+
})
138+
139+
render(<App />)
140+
141+
const todos = screen.getAllByRole('listitem')
142+
expect(todos).toHaveLength(2)
143+
})
144+
})
145+
```
146+
147+
### Mock Selectors
148+
149+
```ts
150+
describe('TodoList', () => {
151+
beforeEach(() => {
152+
mockSlice.beforeEach()
153+
})
154+
155+
it('should render completed todo count', () => {
156+
mockSlice(todosSlice, {
157+
selectCompleted: () => [{ id: 0, name: '🧪 Write unit tests', done: true }]
158+
})
159+
160+
render(<App />)
161+
162+
expect(screen.getByText('Completed todo count: 1')).toBeInTheDocument()
163+
})
164+
})
165+
```
166+
167+
### Mock and Spy on Actions
168+
169+
```ts
170+
describe('TodoList', () => {
171+
beforeEach(() => {
172+
mockSlice.beforeEach()
173+
})
174+
175+
it('should toggle todos', () => {
176+
const { toggleTodo } = mockSlice(todosSlice, {
177+
state: [{ id: 0, name: '🧪 Write unit tests', done: false }]
178+
})
179+
180+
render(<App />)
181+
182+
const todoToggle = screen.getByRole('checkbox')
183+
fireEvent.click(todoToggle)
184+
expect(toggleTodo).toHaveBeenCalled()
185+
})
186+
})
187+
```
188+
189+
**Note:** Calling `beforeEach(() => { mockSlice.beforeEach() })` is required to ensure test cases run in isolation.

0 commit comments

Comments
 (0)