I have a unit test which includes rendering a component that uses useSWR
to fetch data. But the data is not ready before the expect()
is being called so the test fails.
test("StyleOptionDetails contains correct style option number", () => {
renderWithMockSwr(
<StyleOptionDetails
{...mockIStyleOptionDetailsProps}
/>
)
expect(screen.getByText(123)).toBeInTheDocument();
});
But if I put in a delay setTimeout()
, it will pass the test.
setTimeout(() => {
console.log('This will run after 2 second')
expect(screen.getByText(123)).toBeInTheDocument();
}, 2000);
What is the correct way to create a delay or wait for the data?
CodePudding user response:
I have a similar answer elsewhere for a ReactJS question, Web Fetch API (waiting the fetch to complete and then executed the next instruction). Let me thresh out my solution for your problem.
If your function renderWithMockSwr()
is asynchronous, then if you want it to wait to finish executing before calling the next line, use the await
command.
await renderWithMockSwr(
<StyleOptionDetails
{...mockIStyleOptionDetailsProps}
/>
)
async
is wonderful. So is await
. Check it out: Mozilla Developer Network: Async Function
CodePudding user response:
Although I think you are already doing this, the first thing to note is that you shouldn't be actually fetching any data from your tests-- you should be mocking the result.
Once you are doing that, you will use the waitFor
utility to aid in your async testing-- this utility basically accepts a function that returns an expectation (expect
), and will hold at that point of the test until the expectation is met.
Let's provide an example. Take the imaginary component I've created below:
const MyComponent = () => {
const [data, setData] = useState();
useEffect(() => {
MyService.fetchData().then((response) => setData(response));
}, []);
if (!data) {
return (<p>Loading...</p>);
}
// else
return (
<div>
<h1>DATA!</h1>
<div>
{data.map((datum) => (<p>{datum}</p>))}
</div>
</div>
);
}
So for your test, you would do
import MyService from './MyService';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
const mockData = ['Spengler', 'Stanz', 'Venkman', 'Zeddmore'];
beforeEach(() => {
jest.spyOn(MyService, 'fetchData')
.mockImplementation(
() => new Promise((res) => setTimeout(() => res(mockData), 200))
);
});
afterEach(() => {
MyService.fetchData.mockRestore();
});
it('displays the loading first and the data once fetched', async () => {
render(<MyComponent />);
// before fetch completes
expect(screen.getByText(/Loading/)).toBeInTheDocument();
expect(screen.queryByText('DATA!')).toBeNull();
// after fetch completes..
// await waitFor will wait here for this to be true; if it doesn't happen after about five seconds the test fails
await waitFor(() => expect(screen.getByText('DATA!')).toBeInTheDocument());
expect(screen.queryByText(/Loading/)).toBeNull(); // we don't have to await this one because we awaited the proper condition in the previous line
});
});
This isn't tested but something like this should work. Your approach to mocking may vary a bit on account of however you've structured your fetch.