Skip to content
This repository was archived by the owner on Mar 5, 2022. It is now read-only.

Commit 0a8196e

Browse files
authored
feat: add mountHook function for testing React hooks (#335)
1 parent e98a79a commit 0a8196e

File tree

5 files changed

+113
-1
lines changed

5 files changed

+113
-1
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ See [Recipes](./docs/recipes.md) for more examples.
9292
}
9393
```
9494

95+
## API
96+
97+
- `mount` is the most important function, allows to mount a given React component as a mini web application and interact with it using Cypress commands
98+
- `unmount` removes previously mounted component, mostly useful to test how the component cleans up after itself
99+
- `mountHook` mounts a given React Hook in a test component for full testing, see `hooks` example
100+
95101
## Examples
96102

97103
```js
@@ -107,7 +113,7 @@ describe('HelloWorld component', () => {
107113
})
108114
```
109115

110-
Look at the examples in [cypress/component](cypress/component) folder. Here is the list in progress
116+
Look at the examples in [cypress/component](cypress/component) folder. Here is the list of examples showing various testing scenarios.
111117

112118
### Basic examples
113119

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# testing React hooks
2+
3+
- [counter-with-hooks.spec.js](counter-with-hooks.spec.js) and [counter2-with-hooks.spec.js](counter2-with-hooks.spec.js) test React components that uses hooks
4+
- [use-counter.spec.js](use-counter.spec.js) shows how to test a React hook using `mountHook` function
5+
6+
![Hook test](images/hook.png)
7+
8+
Note: hooks are mounted inside a test component following the approach shown in [react-hooks-testing-library](https://github.com/testing-library/react-hooks-testing-library/blob/master/src/pure.js)
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @ts-check
2+
/// <reference types="cypress" />
3+
import React, { useState, useCallback } from 'react'
4+
// @ts-ignore
5+
import { mountHook } from 'cypress-react-unit-test'
6+
7+
// testing example hook function from
8+
// https://dev.to/jooforja/12-recipes-for-testing-react-applications-using-testing-library-1bh2#hooks
9+
function useCounter() {
10+
const [count, setCount] = useState(0)
11+
const increment = useCallback(() => setCount(x => x + 1), [])
12+
return { count, increment }
13+
}
14+
15+
describe('useCounter hook', function() {
16+
it('increments the count', function() {
17+
mountHook(() => useCounter()).then(result => {
18+
expect(result.current.count).to.equal(0)
19+
result.current.increment()
20+
expect(result.current.count).to.equal(1)
21+
result.current.increment()
22+
expect(result.current.count).to.equal(2)
23+
})
24+
})
25+
})

lib/index.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,77 @@ export const unmount = () => {
166166
})
167167
}
168168

169+
// mounting hooks inside a test component mostly copied from
170+
// https://github.com/testing-library/react-hooks-testing-library/blob/master/src/pure.js
171+
function resultContainer() {
172+
let value: any = null
173+
let error: Error | null = null
174+
const resolvers: any[] = []
175+
176+
const result = {
177+
get current() {
178+
if (error) {
179+
throw error
180+
}
181+
return value
182+
},
183+
get error() {
184+
return error
185+
},
186+
}
187+
188+
const updateResult = (val: any, err: Error | null = null) => {
189+
value = val
190+
error = err
191+
resolvers.splice(0, resolvers.length).forEach(resolve => resolve())
192+
}
193+
194+
return {
195+
result,
196+
addResolver: (resolver: any) => {
197+
resolvers.push(resolver)
198+
},
199+
setValue: (val: any) => updateResult(val),
200+
setError: (err: Error) => updateResult(undefined, err),
201+
}
202+
}
203+
204+
// @ts-ignore
205+
function TestHook({ callback, onError, children }) {
206+
try {
207+
children(callback())
208+
} catch (err) {
209+
if (err.then) {
210+
throw err
211+
} else {
212+
onError(err)
213+
}
214+
}
215+
216+
// TODO decide what the test hook component should show
217+
// maybe nothing, or maybe useful information about the hook?
218+
// maybe its current properties?
219+
// return <div>TestHook</div>
220+
return null
221+
}
222+
223+
/**
224+
* Mounts a React hook function in a test component for testing.
225+
*
226+
* @see https://github.com/bahmutov/cypress-react-unit-test#advanced-examples
227+
*/
228+
export const mountHook = (hookFn: Function) => {
229+
const { result, setValue, setError } = resultContainer()
230+
231+
return mount(
232+
React.createElement(TestHook, {
233+
callback: hookFn,
234+
onError: setError,
235+
children: setValue,
236+
}),
237+
).then(() => {
238+
cy.wrap(result)
239+
})
240+
}
241+
169242
export default mount

0 commit comments

Comments
 (0)