Reputation: 2216
I have a website made with React.JS that is continuously emitting events for everything [and nothing] that is happening. For example, users typed something in the form, emit an event. Users set focus on a field and do not do anything for some time, we again emit an event. The idea is to understand customers' behavior.
I have a button what used to work as <Button onClick={(e)=>handler(e)}/>
. I had to debounce the button and change it to <Button onClick={(e)=>setTimeout(handler(e), 1000)}/>
. Otherwise, users did not understand what was happening.
Now, I am trying to adjust the tests written as:
it('displays similar listings', async () => {
const renderResult = renderVdp(renderParams);
await new Promise((resolve) => setTimeout(resolve, 1000));
await waitFor(async () => {
expect(renderResult.getAllByText('2017 BMW M3')).toHaveLength(4);
});
});
I was able to keep the original tests working by adding the await new Promise((resolve) => setTimeout(resolve, 1000));
before relevant tests. Without it, tests receive the next emitted event, which is not the event emitted as a response to clicking on the button.
I would like to use jest timer mocking or something similar, instead of actually introducing delays inside my tests. I have tried using jest.useFakeTimers()
, but it does not work as I need it to. My expects are failing.
Suggestions?
Upvotes: 5
Views: 15815
Reputation: 181
Once you use fake timers, you control when to run them. Try to run them after you render:
it('displays similar listings', async () => {
jest.useFakeTimers()
const renderResult = renderVdp(renderParams);
jest.runAllTimers();
expect(renderResult.getAllByText('2017 BMW M3')).toHaveLength(4);
});
If that's not working, you can always use a more "aggressive" mock:
global.setTimeout = jest.fn(cb => cb());
I would do it in the test setup beforeEach
/ beforeAll
and rollback to original setTimeout in the test teardown afterEach
/ afterAll
Upvotes: 10
Reputation: 958
It is hard to tell how the test should look like if I can't see the code.
If your component works a little bit like this:
const SetTimeoutComponent = () => {
const [content, setContent] = React.useState<string[]>([]);
const handleButtonClick = (event: React.MouseEvent<HTMLElement>) => {
setContent([
'2017 BMW M3',
'2017 BMW M3',
'2017 BMW M3',
'2017 BMW M3'
])
}
return (
<div>
<button onClick={(e)=>setTimeout(() => handleButtonClick(e), 1000)}>
Set timeout with state update
</button>
<div>
{content.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
</div>
);
};
then you can test it as follows:
it('displays similar listings', async () => {
render(<TestedComponent />);
const setTimeoutButton = screen.getByRole('button');
jest.useFakeTimers();
fireEvent.click(setTimeoutButton);
act(() => {
jest.runAllTimers();
});
expect(screen.getAllByText('2017 BMW M3')).toHaveLength(4);
jest.useRealTimers();
});
You can check more in my post.
Upvotes: 2