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

Commit 22fd85f

Browse files
authored
feat: add wrapper option to mountHook function (#536)
2 parents 6481d07 + 593954c commit 22fd85f

File tree

10 files changed

+157
-10
lines changed

10 files changed

+157
-10
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ See [Recipes](./docs/recipes.md) for more examples.
126126
- `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
127127
- `createMount` factory function that creates new `mount` function with default options
128128
- `unmount` removes previously mounted component, mostly useful to test how the component cleans up after itself
129-
- `mountHook` mounts a given React Hook in a test component for full testing, see `hooks` example
129+
- `mountHook` mounts a given React Hook in a test component for full testing, see the [`hooks` example](cypress/component/advanced/hooks)
130130

131131
## Examples
132132

@@ -191,7 +191,7 @@ Spec | Description
191191
[context](cypress/component/advanced/context) | Confirms components that use React context feature work
192192
[custom-command](cypress/component/advanced/custom-command) | Wraps `mount` in a custom command for convenience
193193
[forward-ref](cypress/component/advanced/forward-ref) | Tests a component that uses a forward ref feature
194-
[hooks](cypress/component/advanced/hooks) | Tests several components that use React Hooks like `useState`, `useCallback`
194+
[hooks](cypress/component/advanced/hooks) | Tests several components that use React Hooks like `useState`, `useCallback` by using `mountHook` function
195195
[lazy-loaded](cypress/component/advanced/lazy-loaded) | Confirms components that use `React.lazy` and dynamic imports work
196196
[material-ui-example](cypress/component/advanced/material-ui-example) | Large components demos from [Material UI](https://material-ui.com/)
197197
[mobx-v6](cypress/component/advanced/mobx-v6) | Test components with MobX v6 observable

cypress/component/advanced/hooks/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,19 @@
22

33
- [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
44
- [use-counter.spec.js](use-counter.spec.js) shows how to test a React hook using `mountHook` function
5+
- [custom-hook.mount-spec.js](custom-hook.mount-spec.js) manually creates a wrapper component around a custom hook that uses Redux provider
6+
- [custom-hook.mount-hook-spec.js](custom-hook.mount-hook-spec.js) shows how `mountHook` can be surrounded with `wrapper` element
57

68
![Hook test](images/hook.png)
79

810
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)
11+
12+
Example:
13+
14+
```js
15+
import { mountHook } from 'cypress-react-unit-test'
16+
// wrapper is optional, only if your hook requires
17+
// something like a context provider
18+
const wrapper = ({ children }) => <Provider store={store}>{children}</Provider>
19+
mountHook(() => useCustomHook(), { wrapper })
20+
```
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const countReducer = function(state = 0, action) {
2+
switch (action.type) {
3+
case 'INCREMENT':
4+
return state + 1
5+
case 'DECREMENT':
6+
return state - 1
7+
default:
8+
return state
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useEffect } from 'react'
2+
import { useDispatch } from 'react-redux'
3+
4+
export function useCustomHook() {
5+
useDispatch()
6+
7+
useEffect(() => {
8+
console.log('hello world!')
9+
}, [])
10+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference types="cypress" />
2+
import React from 'react'
3+
import { mountHook } from 'cypress-react-unit-test'
4+
const { useCustomHook } = require('./custom-hook')
5+
import { Provider } from 'react-redux'
6+
import store from './store'
7+
8+
describe('custom hook that needs redux provider', () => {
9+
it('mounted with wrapper', () => {
10+
const wrapper = ({ children }) => (
11+
<Provider store={store}>{children}</Provider>
12+
)
13+
14+
cy.spy(console, 'log').as('log')
15+
mountHook(() => useCustomHook(), { wrapper })
16+
17+
// make sure the custom hook calls "useEffect"
18+
cy.get('@log').should('have.been.calledWith', 'hello world!')
19+
})
20+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference types="cypress" />
2+
import React from 'react'
3+
import { mount } from 'cypress-react-unit-test'
4+
const { useCustomHook } = require('./custom-hook')
5+
import { Provider } from 'react-redux'
6+
import store from './store'
7+
8+
describe('custom hook that needs redux provider', () => {
9+
it('mounts if we make a test component around it', () => {
10+
const App = () => {
11+
useCustomHook()
12+
13+
return <></>
14+
}
15+
cy.spy(console, 'log').as('log')
16+
mount(
17+
<Provider store={store}>
18+
<App />
19+
</Provider>,
20+
)
21+
cy.get('@log').should('have.been.calledWith', 'hello world!')
22+
})
23+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createStore } from 'redux'
2+
import { countReducer } from './count-reducer'
3+
4+
const store = createStore(countReducer)
5+
6+
export default store

lib/mountHook.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,39 @@ function TestHook({ callback, onError, children }: TestHookProps) {
6060
return null
6161
}
6262

63+
type MountHookOptions = {
64+
wrapper?: React.ReactElement
65+
}
66+
6367
/**
6468
* Mounts a React hook function in a test component for testing.
6569
*
6670
* @see https://github.com/bahmutov/cypress-react-unit-test#advanced-examples
6771
*/
68-
export const mountHook = (hookFn: (...args: any[]) => any) => {
72+
export const mountHook = (
73+
hookFn: (...args: any[]) => any,
74+
options: MountHookOptions = {},
75+
) => {
6976
const { result, setValue, setError } = resultContainer()
7077

71-
return mount(
72-
React.createElement(TestHook, {
73-
callback: hookFn,
74-
onError: setError,
75-
children: setValue,
76-
}),
77-
).then(() => {
78+
const testElement = React.createElement(TestHook, {
79+
callback: hookFn,
80+
onError: setError,
81+
children: setValue,
82+
key: Math.random().toString(),
83+
})
84+
85+
let mountElement: any = testElement
86+
if (options.wrapper) {
87+
// what's the proper type? I don't even care anymore
88+
// because types for React seem to be a mess
89+
// @ts-ignore
90+
mountElement = React.createElement(options.wrapper, {
91+
children: [testElement],
92+
})
93+
}
94+
95+
return mount(mountElement).then(() => {
7896
cy.wrap(result)
7997
})
8098
}

package-lock.json

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,11 @@
114114
"react-google-maps": "9.4.5",
115115
"react-i18next": "11.7.2",
116116
"react-loading-skeleton": "2.0.1",
117+
"react-redux": "7.2.2",
117118
"react-router": "6.0.0-alpha.1",
118119
"react-router-dom": "6.0.0-alpha.1",
119120
"react-scripts": "3.4.1",
121+
"redux": "4.0.5",
120122
"rollup-plugin-istanbul": "2.0.1",
121123
"semantic-release": "17.2.2",
122124
"standard": "14.3.3",

0 commit comments

Comments
 (0)