-
Notifications
You must be signed in to change notification settings - Fork 232
Add waitForNextUpdate
promise for testing async updates
#11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,11 +9,17 @@ function TestHook({ callback, hookProps, children }) { | |
function renderHook(callback, { initialProps, ...options } = {}) { | ||
const result = { current: null } | ||
const hookProps = { current: initialProps } | ||
const resolvers = [] | ||
const waitForNextUpdate = () => | ||
new Promise((resolve) => { | ||
resolvers.push(resolve) | ||
}) | ||
|
||
const toRender = () => ( | ||
<TestHook callback={callback} hookProps={hookProps.current}> | ||
{(res) => { | ||
result.current = res | ||
resolvers.splice(0, resolvers.length).forEach((resolve) => resolve()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we pass res to resolve? Like this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did start out with that, but decided that, as there is already a way of accessing the value, it might be confusing to use different variables before and after the test('something', async () => {
const { result, nextValue } = renderHook(() => useSomeHook())
expect(result.current).toBe({/* ... */})
expect(await nextValue()).toBe({/* ... */})
}) It also falls down when you want to assert a nexted value test('something', async () => {
const { result, nextValue } = renderHook(() => useSomeHook())
expect(result.current.someProp).toBe({/* ... */})
// doesn't work
// expect(await nextValue().someProp).toBe({/* ... */})
const next = await nextValue()
expect(next.someProp).toBe({/* ... */})
}) or multiple assertions test('something', async () => {
const { result, nextValue } = renderHook(() => useSomeHook())
expect(result.current).not.toContain({/* ... */})
expect(result.current).toHaveLength(2)
// fine
// expect(await nextValue()).toContain({/* ... */})
// hangs until the test fails
// expect(await nextValue()).toHaveLength(3)
const next = await nextValue()
expect(next).toContain({/* ... */})
expect(next).toHaveLength(3)
}) It also raises questions about whether it should fire if the If we wanted to go this way, I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do agree with you. Anyway, we can access the current result right after we awaited so it doesn't really matter. About the method's name, I am wondering if it wouldn't be better to have something like the async methods in react-testing-library. I am talking about the wait, waitForElement and waitForDomChange methods. Maybe a name like waitForNextUpdate or waitForNextResult would provide a better description? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I like that. @ab18556 if you want to submit a PR adding yourself as a contributor ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't feel i did much, but since it was my first contribution to a public project, I am happy to do it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You had more input on this PR than anyone else, and as a library author and maintainer, having anyone actually challenging and validating your work is invaluable and a fantastic contribution you can make (second only to updating the documentation 😉). |
||
}} | ||
</TestHook> | ||
) | ||
|
@@ -22,6 +28,7 @@ function renderHook(callback, { initialProps, ...options } = {}) { | |
|
||
return { | ||
result, | ||
waitForNextUpdate, | ||
unmount, | ||
rerender: (newProps = hookProps.current) => { | ||
hookProps.current = newProps | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { useState, useEffect } from 'react' | ||
import { renderHook, cleanup } from 'src' | ||
|
||
describe('async hook tests', () => { | ||
const getSomeName = () => Promise.resolve('Betty') | ||
|
||
const useName = (prefix) => { | ||
const [name, setName] = useState('nobody') | ||
|
||
useEffect(() => { | ||
getSomeName().then((theName) => { | ||
setName(prefix ? `${prefix} ${theName}` : theName) | ||
}) | ||
}, [prefix]) | ||
|
||
return name | ||
} | ||
|
||
afterEach(cleanup) | ||
|
||
test('should wait for next update', async () => { | ||
const { result, waitForNextUpdate } = renderHook(() => useName()) | ||
|
||
expect(result.current).toBe('nobody') | ||
|
||
await waitForNextUpdate() | ||
|
||
expect(result.current).toBe('Betty') | ||
}) | ||
|
||
test('should wait for multiple updates', async () => { | ||
const { result, waitForNextUpdate, rerender } = renderHook(({ prefix }) => useName(prefix), { | ||
initialProps: { prefix: 'Mrs.' } | ||
}) | ||
|
||
expect(result.current).toBe('nobody') | ||
|
||
await waitForNextUpdate() | ||
|
||
expect(result.current).toBe('Mrs. Betty') | ||
|
||
rerender({ prefix: 'Ms.' }) | ||
|
||
await waitForNextUpdate() | ||
|
||
expect(result.current).toBe('Ms. Betty') | ||
}) | ||
|
||
test('should resolve all when updating', async () => { | ||
const { result, waitForNextUpdate } = renderHook(({ prefix }) => useName(prefix), { | ||
initialProps: { prefix: 'Mrs.' } | ||
}) | ||
|
||
expect(result.current).toBe('nobody') | ||
|
||
await Promise.all([waitForNextUpdate(), waitForNextUpdate(), waitForNextUpdate()]) | ||
|
||
expect(result.current).toBe('Mrs. Betty') | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops! these must have snuck into master when I was updating the README a few days ago. Nonetheless, they are not required.