Skip to content

Commit 59182d8

Browse files
committed
Merge master in pr/async-utils
2 parents c5099d9 + a63b6f9 commit 59182d8

11 files changed

+200
-10
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ Please fill out the information below to expedite the review and (hopefully)
1414
merge of your pull request!
1515
-->
1616

17-
1817
**What**:
1918

2019
<!-- What changes are being made? (What feature/bug is being fixed here?) -->
@@ -38,7 +37,6 @@ Remove any items that are relevant to your changes
3837

3938
- [ ] Documentation updated
4039
- [ ] Tests
41-
- [ ] Typescript definitions updated
4240
- [ ] Ready to be merged
4341
<!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->
4442
- [ ] Added myself to contributors table

docs/api-reference.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ route: '/reference/api'
99

1010
- [`renderHook`](/reference/api#renderhook)
1111
- [`act`](/reference/api#act)
12+
- [`cleanup`](/reference/api#cleanup)
1213

1314
---
1415

@@ -101,6 +102,43 @@ This is the same [`act` function](https://reactjs.org/docs/test-utils.html#act)
101102

102103
---
103104

105+
## `cleanup`
106+
107+
```js
108+
function cleanup: Promise<void>
109+
```
110+
111+
Unmounts any rendered hooks rendered with `renderHook`, ensuring all effects have been flushed.
112+
113+
> Please note that this is done automatically if the testing framework you're using supports the
114+
> `afterEach` global (like Jest, mocha and Jasmine). If not, you will need to do manual cleanups
115+
> after each test.
116+
117+
The `cleanup` function should be called after each test to ensure that previously rendered hooks
118+
will not have any unintended side-effects on the following tests.
119+
120+
### Skipping Auto-Cleanup
121+
122+
Importing `@testing-library/react-hooks/dont-cleanup-after-each.js` in test setup files will disable
123+
the auto-cleanup feature.
124+
125+
For example, in [Jest](https://jestjs.io/) this can be added to your
126+
[Jest config](https://jestjs.io/docs/configuration):
127+
128+
```js
129+
module.exports = {
130+
setupFilesAfterEnv: [
131+
'@testing-library/react-hooks/dont-cleanup-after-each.js'
132+
// other setup files
133+
]
134+
}
135+
```
136+
137+
Alternatively, setting the `RHTL_SKIP_AUTO_CLEANUP` environment variable to `true` before importing
138+
`@testing-library/react-hooks` will also disable this feature.
139+
140+
---
141+
104142
## Async Utilities
105143
106144
### `waitForNextUpdate`

dont-cleanup-after-each.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
process.env.RHTL_SKIP_AUTO_CLEANUP = true

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@testing-library/react-hooks",
3-
"version": "2.0.3",
3+
"version": "3.1.0",
44
"description": "Simple and complete React hooks testing utilities that encourage good testing practices.",
55
"main": "lib/index.js",
66
"keywords": [

src/cleanup.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { act } from 'react-test-renderer'
2+
3+
let cleanupCallbacks = []
4+
5+
async function cleanup() {
6+
await act(async () => {})
7+
cleanupCallbacks.forEach((cb) => cb())
8+
cleanupCallbacks = []
9+
}
10+
11+
function addCleanup(callback) {
12+
cleanupCallbacks.push(callback)
13+
}
14+
15+
function removeCleanup(callback) {
16+
cleanupCallbacks = cleanupCallbacks.filter((cb) => cb !== callback)
17+
}
18+
19+
// Automatically registers cleanup in supported testing frameworks
20+
if (typeof afterEach === 'function' && !process.env.RHTL_SKIP_AUTO_CLEANUP) {
21+
afterEach(async () => {
22+
await cleanup()
23+
})
24+
}
25+
26+
export { cleanup, addCleanup, removeCleanup }

src/index.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { Suspense } from 'react'
22
import { act, create } from 'react-test-renderer'
33
import asyncUtils from './asyncUtils'
4+
import { cleanup, addCleanup, removeCleanup } from './cleanup'
45

56
function TestHook({ callback, hookProps, onError, children }) {
67
try {
@@ -74,6 +75,15 @@ function renderHook(callback, { initialProps, wrapper } = {}) {
7475
})
7576
const { unmount, update } = testRenderer
7677

78+
function unmountHook() {
79+
act(() => {
80+
removeCleanup(unmountHook)
81+
unmount()
82+
})
83+
}
84+
85+
addCleanup(unmountHook)
86+
7787
return {
7888
result,
7989
rerender: (newProps = hookProps.current) => {
@@ -82,13 +92,9 @@ function renderHook(callback, { initialProps, wrapper } = {}) {
8292
update(toRender())
8393
})
8494
},
85-
unmount: () => {
86-
act(() => {
87-
unmount()
88-
})
89-
},
95+
unmount: unmountHook,
9096
...asyncUtils(addResolver)
9197
}
9298
}
9399

94-
export { renderHook, act }
100+
export { renderHook, cleanup, act }

test/autoCleanup.disabled.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useEffect } from 'react'
2+
3+
// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set
4+
// then we DON'T auto-wire up the afterEach for folks
5+
describe('skip auto cleanup (disabled) tests', () => {
6+
let cleanupCalled = false
7+
let renderHook
8+
9+
beforeAll(() => {
10+
process.env.RHTL_SKIP_AUTO_CLEANUP = 'true'
11+
renderHook = require('src').renderHook
12+
})
13+
14+
test('first', () => {
15+
const hookWithCleanup = () => {
16+
useEffect(() => {
17+
return () => {
18+
cleanupCalled = true
19+
}
20+
})
21+
}
22+
renderHook(() => hookWithCleanup())
23+
})
24+
25+
test('second', () => {
26+
expect(cleanupCalled).toBe(false)
27+
})
28+
})

test/autoCleanup.noAfterEach.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useEffect } from 'react'
2+
3+
// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set
4+
// then we DON'T auto-wire up the afterEach for folks
5+
describe('skip auto cleanup (no afterEach) tests', () => {
6+
let cleanupCalled = false
7+
let renderHook
8+
9+
beforeAll(() => {
10+
afterEach = false
11+
renderHook = require('src').renderHook
12+
})
13+
14+
test('first', () => {
15+
const hookWithCleanup = () => {
16+
useEffect(() => {
17+
return () => {
18+
cleanupCalled = true
19+
}
20+
})
21+
}
22+
renderHook(() => hookWithCleanup())
23+
})
24+
25+
test('second', () => {
26+
expect(cleanupCalled).toBe(false)
27+
})
28+
})

test/autoCleanup.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useEffect } from 'react'
2+
import { renderHook } from 'src'
3+
4+
// This verifies that by importing RHTL in an
5+
// environment which supports afterEach (like jest)
6+
// we'll get automatic cleanup between tests.
7+
describe('auto cleanup tests', () => {
8+
let cleanupCalled = false
9+
10+
test('first', () => {
11+
const hookWithCleanup = () => {
12+
useEffect(() => {
13+
return () => {
14+
cleanupCalled = true
15+
}
16+
})
17+
}
18+
renderHook(() => hookWithCleanup())
19+
})
20+
21+
test('second', () => {
22+
expect(cleanupCalled).toBe(true)
23+
})
24+
})

test/cleanup.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useEffect } from 'react'
2+
import { renderHook, cleanup } from 'src'
3+
4+
describe('cleanup tests', () => {
5+
test('should flush effects on cleanup', async () => {
6+
let cleanupCalled = false
7+
8+
const hookWithCleanup = () => {
9+
useEffect(() => {
10+
return () => {
11+
cleanupCalled = true
12+
}
13+
})
14+
}
15+
16+
renderHook(() => hookWithCleanup())
17+
18+
await cleanup()
19+
20+
expect(cleanupCalled).toBe(true)
21+
})
22+
23+
test('should cleanup all rendered hooks', async () => {
24+
let cleanupCalled = []
25+
const hookWithCleanup = (id) => {
26+
useEffect(() => {
27+
return () => {
28+
cleanupCalled[id] = true
29+
}
30+
})
31+
}
32+
33+
renderHook(() => hookWithCleanup(1))
34+
renderHook(() => hookWithCleanup(2))
35+
36+
await cleanup()
37+
38+
expect(cleanupCalled[1]).toBe(true)
39+
expect(cleanupCalled[2]).toBe(true)
40+
})
41+
})

0 commit comments

Comments
 (0)