react-hooks-testing-library version: 3.2.1react-test-renderer version: 16.13.1 react version: 16.13.0node version: v12.14.0npm (or yarn) version: npm 6.13.4The code below is a simplified example, but this is the link to the real repo/test file
import ContextProvider from './ContextProviderWrappper'
const createComponent = (state, ready = true) => {
const Component = ({ children }) => (
<ContextProvider>
<ComponentThatAddsStateToContext />
{children}
</ContextProvider>
);
return {
Component,
store
}
}
describe('Thing thats not working', () => {
afterEach(cleanup)
test('something that renders the same Component', async () => {
const { Component } = createComponent()
const { container } = render(<Component />)
expect(container.firstChild).toBeAnything()
})
test('it adds the property to context state', async () => {
const { Component } = createComponent()
const { result, waitForNextUpdate } = renderHook(
() => useHookThatGrabsValuesFromContext(),
{
wrapper: Component
}
)
// hangs when 1st test is run, works as expected when its the only test in the file
await waitForNextUpdate()
expect(result.current.property).not.toBeNull()
})
})
I'm trying to test that rendering a component eventually adds some piece of information to a state that's stored in React Context.
When I run _just_ the second test (the one that hangs when I also include the first), the desired and expected behavior are the same:
describe('Thing thats not working', () => {
afterEach(cleanup)
test('it adds the property to context state', async () => {
const { Component } = createComponent()
const { result, waitForNextUpdate } = renderHook(
() => useHookThatGrabsValuesFromContext(),
{
wrapper: Component
}
)
// at this point result.current is the initial context's state, as it should be
await waitForNextUpdate()
// at this point, result.current is the updated context's state, as it should be
expect(result.current.property).not.toBeNull()
// test passes!
})
})
But when I include the first test as well, I can see in my logs that the initial state gets updated _before_ waitForNextUpdate gets called (and my logs also show that the Context takes on its initial state when rendered, showing state's aren't bleeding across test cases). waitForNextUpdate hangs because there are no updates left to make.
describe('Thing thats not working', () => {
afterEach(cleanup)
test('something that renders the same Component', async () => {
const { Component } = createComponent()
const { container } = render(<Component />)
expect(container.firstChild).toBeAnything()
})
test('it adds the property to context state', async () => {
const { Component } = createComponent()
const { result, waitForNextUpdate } = renderHook(
() => useHookThatGrabsValuesFromContext(),
{
wrapper: Component
}
)
// at this point result.current is the updated context's state
await waitForNextUpdate()
// at this point, the call hangs because no more state updates are being made
// unreachable code bvecause of hanging test
expect(result.current.property).not.toBeNull()
})
})
At first, since the Context Provider wrapper references the same context, I thought (even after cleaning up and unmounting components) two separate tests might reference the same context and cause interference. (I do this because my Context Provider component is pretty hefty, and I don't want to recreate it _just_ for tests that don't test it). Which would mean state updates in test 1 were carried over to test 2. I verified this is not the case by rendering completely different Contexts with the same result. I also verified that the context state _does_ start at initial state, and get updated to updated state, in test 2 before waitForNextUpdate
Also, switching the order of these tests solves the problem.
It's not a show stopper, but it's been bugging me (no pun!). Ordering of tests causes a useReducer state to not respect waitForNextUpdate.
Not sure yet.
An additional observation, rendering the passing test twice in a row causes the same issue (first passes, second hangs).
I am experiencing the same issue with an entirely different hook, though I am able to "fix" this.
Here is my hook that fetches data:
function useAsyncData(fetcher) {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
async function getData() {
try {
const result = await fetcher();
setData(result);
} catch (e) {
setError(e)
}
setIsLoading(false);
}
getData()
}, [fetcher]);
return { isLoading, data, error };
}
and here are the tests for it (simplified):
const mockFetcher = jest.fn();
describe('useAsyncData', () => {
it('should return data', async () => {
mockFetcher.mockImplementationOnce(() => Promise.resolve('data'));
const { result, waitForNextUpdate } = renderHook(() =>
useAsyncData(mockFetcher),
);
await waitForNextUpdate();
expect(result.current.data).toEqual('data');
});
it('should return error', async () => {
mockFetcher.mockImplementationOnce(() => Promise.reject('error'));
const { result, waitForNextUpdate } = renderHook(() =>
useAsyncData(mockFetcher),
);
await waitForNextUpdate();
expect(result.current.error).toEqual('error');
});
});
When I run both tests the second one would fail with jest timeout error. However, if I run them individually they will pass. I am able to fix this by creating a separate mock function for each tests instead of creating a global one and mocking its implementation in each test:
it('should return data', async () => {
const resolvedFetcher = jest.fn(() => Promise.resolve('data'));
// ...
});
it('should return error', async () => {
const rejectedFetcher = jest.fn(() => Promise.reject('error'));
// ...
});
I don't know why it fixes the issue though.
Oddly enough, I am not able to reproduce this in a new React Native project with the exact same version of jest and @testing-library/react-hooks and the exact same code. I tried clearing jest caches and reinstalling the libraries. Nothing works.
Sorry @Schwartz10 and @MrCreeper1008, I'm struggling to find time to look into this.
Given that react-testing-library and react-hooks-testing-library use completely different renderers (react-dom and react-test-renderer respectively), it's highly unlikely that rendering the component with one library is breaking the other.
It feels like there is some shared state between the tests that is causing some cross-talk when the tests are run together. Generally, you should avoid sharing anything between your tests unless there is a way to reset it between tests (jest mocks, for example).
Let me know if this is enough of a thread to follow to search for some answers and if you find the issue in your tests.
Most helpful comment
I am experiencing the same issue with an entirely different hook, though I am able to "fix" this.
Here is my hook that fetches data:
and here are the tests for it (simplified):
When I run both tests the second one would fail with jest timeout error. However, if I run them individually they will pass. I am able to fix this by creating a separate mock function for each tests instead of creating a global one and mocking its implementation in each test:
I don't know why it fixes the issue though.