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

feat: add wrapper option to mountHook function #536

Merged
merged 3 commits into from
Nov 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ See [Recipes](./docs/recipes.md) for more examples.
- `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
- `createMount` factory function that creates new `mount` function with default options
- `unmount` removes previously mounted component, mostly useful to test how the component cleans up after itself
- `mountHook` mounts a given React Hook in a test component for full testing, see `hooks` example
- `mountHook` mounts a given React Hook in a test component for full testing, see the [`hooks` example](cypress/component/advanced/hooks)

## Examples

Expand Down Expand Up @@ -191,7 +191,7 @@ Spec | Description
[context](cypress/component/advanced/context) | Confirms components that use React context feature work
[custom-command](cypress/component/advanced/custom-command) | Wraps `mount` in a custom command for convenience
[forward-ref](cypress/component/advanced/forward-ref) | Tests a component that uses a forward ref feature
[hooks](cypress/component/advanced/hooks) | Tests several components that use React Hooks like `useState`, `useCallback`
[hooks](cypress/component/advanced/hooks) | Tests several components that use React Hooks like `useState`, `useCallback` by using `mountHook` function
[lazy-loaded](cypress/component/advanced/lazy-loaded) | Confirms components that use `React.lazy` and dynamic imports work
[material-ui-example](cypress/component/advanced/material-ui-example) | Large components demos from [Material UI](https://material-ui.com/)
[mobx-v6](cypress/component/advanced/mobx-v6) | Test components with MobX v6 observable
Expand Down
12 changes: 12 additions & 0 deletions cypress/component/advanced/hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@

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

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

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)

Example:

```js
import { mountHook } from 'cypress-react-unit-test'
// wrapper is optional, only if your hook requires
// something like a context provider
const wrapper = ({ children }) => <Provider store={store}>{children}</Provider>
mountHook(() => useCustomHook(), { wrapper })
```
10 changes: 10 additions & 0 deletions cypress/component/advanced/hooks/count-reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const countReducer = function(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
10 changes: 10 additions & 0 deletions cypress/component/advanced/hooks/custom-hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useEffect } from 'react'
import { useDispatch } from 'react-redux'

export function useCustomHook() {
useDispatch()

useEffect(() => {
console.log('hello world!')
}, [])
}
20 changes: 20 additions & 0 deletions cypress/component/advanced/hooks/custom-hook.mount-hook-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference types="cypress" />
import React from 'react'
import { mountHook } from 'cypress-react-unit-test'
const { useCustomHook } = require('./custom-hook')
import { Provider } from 'react-redux'
import store from './store'

describe('custom hook that needs redux provider', () => {
it('mounted with wrapper', () => {
const wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
)

cy.spy(console, 'log').as('log')
mountHook(() => useCustomHook(), { wrapper })

// make sure the custom hook calls "useEffect"
cy.get('@log').should('have.been.calledWith', 'hello world!')
})
})
23 changes: 23 additions & 0 deletions cypress/component/advanced/hooks/custom-hook.mount-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'
const { useCustomHook } = require('./custom-hook')
import { Provider } from 'react-redux'
import store from './store'

describe('custom hook that needs redux provider', () => {
it('mounts if we make a test component around it', () => {
const App = () => {
useCustomHook()

return <></>
}
cy.spy(console, 'log').as('log')
mount(
<Provider store={store}>
<App />
</Provider>,
)
cy.get('@log').should('have.been.calledWith', 'hello world!')
})
})
6 changes: 6 additions & 0 deletions cypress/component/advanced/hooks/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createStore } from 'redux'
import { countReducer } from './count-reducer'

const store = createStore(countReducer)

export default store
34 changes: 26 additions & 8 deletions lib/mountHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,39 @@ function TestHook({ callback, onError, children }: TestHookProps) {
return null
}

type MountHookOptions = {
wrapper?: React.ReactElement
}

/**
* Mounts a React hook function in a test component for testing.
*
* @see https://github.com/bahmutov/cypress-react-unit-test#advanced-examples
*/
export const mountHook = (hookFn: (...args: any[]) => any) => {
export const mountHook = (
hookFn: (...args: any[]) => any,
options: MountHookOptions = {},
) => {
const { result, setValue, setError } = resultContainer()

return mount(
React.createElement(TestHook, {
callback: hookFn,
onError: setError,
children: setValue,
}),
).then(() => {
const testElement = React.createElement(TestHook, {
callback: hookFn,
onError: setError,
children: setValue,
key: Math.random().toString(),
})

let mountElement: any = testElement

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any => React.Element

if (options.wrapper) {
// what's the proper type? I don't even care anymore
// because types for React seem to be a mess

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not true. React types are better than Vue, and may be better than Angular !

// @ts-ignore
mountElement = React.createElement(options.wrapper, {
children: [testElement],
})
}

return mount(mountElement).then(() => {
cy.wrap(result)
})
}
46 changes: 46 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,11 @@
"react-google-maps": "9.4.5",
"react-i18next": "11.7.2",
"react-loading-skeleton": "2.0.1",
"react-redux": "7.2.2",
"react-router": "6.0.0-alpha.1",
"react-router-dom": "6.0.0-alpha.1",
"react-scripts": "3.4.1",
"redux": "4.0.5",
"rollup-plugin-istanbul": "2.0.1",
"semantic-release": "17.2.2",
"standard": "14.3.3",
Expand Down