RTL await waitFor vs await findBy* - reactjs

I'm not really understand how to use async method in RTL. In the docs they said findBy method is combination of getBy and waitFor. Is there any specific case when to use one of it?
My case is:
Show input elements after success fetching data
Fire event change of each input
Fire event click on submit button
With this approach, my test always failed and return Received length: 0
render(<Component {...mockProps} />);
const inputs = await screen.findAllByRole('textbox');
expect(inputs).toHaveLength(5); //failed
But with this approach, my test get passed
render(<Component {...mockProps} />);
await waitFor(() => {
const inputs = screen.getAllByRole('textbox');
expect(inputs).toHaveLength(5); //passed
});
How to get passed test with the first approach?
Because i want to call async method again after the inputs shows, should i do this?
await waitFor(async () => {
const inputs = screen.getAllByRole('textbox');
expect(inputs).toHaveLength(5);
const [input1, input2, ...etc] = inputs;
fireEvent.change(input1, {target: {value: 'first'}};
...etc
await waitFor(() => {
expect(onSubmit).toHaveBeenCalled();
})
});

You can have separate waitFor()s like this:
await waitFor(async () => {
const inputs = screen.getAllByRole('textbox');
expect(inputs).toHaveLength(5);
const [input1, input2, ...etc] = inputs;
fireEvent.change(input1, {target: {value: 'first'}};
...etc
});
await waitFor(() => {
expect(onSubmit).toHaveBeenCalled();
})
If you want to stick with the first approach, you can use a findBy along with and getBy so you wait for the element to exist before "getting" it:
render(<Component {...mockProps} />);
await screen.findAllByRole('textbox');
const inputs = screen.getAllByRole('textbox');
expect(inputs).toHaveLength(5);
const [input1, input2, ...etc] = inputs;
fireEvent.change(input1, {target: {value: 'first'}};
...etc
await waitFor(() => {
expect(onSubmit).toHaveBeenCalled();
})

Related

Cannot add a promise implementation on jest mock function outside test block

const mockedLogin = jest.fn(() => Promise.resolve()); //does not work if implementation is done here: "cannot read then of undefined"
jest.mock("../../hooks/useAuth", () => {
return () => ({
login: mockedLogin,
});
});
test("submitting the form calls onSubmit with username and password", async () => {
mockedLogin.mockImplementation(() => Promise.resolve()); //works fine here
render(<Login />);
const username = "name";
const password = "i need no password";
await userEvent.type(screen.getByLabelText(/username/i), username);
await userEvent.type(screen.getByLabelText(/password/i), password);
await userEvent.click(screen.getByRole("button", { name: /submit/i }));
expect(mockedLogin).toHaveBeenCalledWith({
username,
password,
});
expect(mockedLogin).toHaveBeenCalledTimes(1);
});
I am trying to mock login function returned by a custom hook.
The login function returns a promise.
If I use mockImplementation inside the test block it works fine but if I use it outside of the test block mockedLogin returns undefined.
Have you tried:
const mockedLogin = jest.fn().mockResolvedValue();

ReactJS - RTL await

I'm trying to use async method in RTL to get the elements which would appear after fetching data. In the docs they said findBy method is combination of getBy and waitFor. Is there any specific case when to use one of it?
My case is:
Show input elements after success fetching data
Fire event change of each input
Fire event click on submit button
Show alert after success
With this approach, my test always failed and return Received length: 0
render(<Component {...mockProps} />);
const inputs = await screen.findAllByRole('textbox');
expect(inputs).toHaveLength(5); //failed
But with this approach, my test get passed
render(<Component {...mockProps} />);
await waitFor(() => {
const inputs = screen.getAllByRole('textbox');
expect(inputs).toHaveLength(5); //passed
});
How to get passed test with the first approach?
Because i want to call async method again after the form submitted, should i do this?
await waitFor(async () => {
const inputs = screen.getAllByRole('textbox');
expect(inputs).toHaveLength(5);
const [input1, input2, ...etc] = inputs;
fireEvent.change(input1, {target: {value: 'first'}};
...etc
await waitFor(() => {
expect(screen.getByRole('alert')).toBeInTheDocument();
})
});

How to test for return result from callback in RTL?

I expect callback to return either true or false. I know how to test for function being called, but how to test if that function returned specific value? How can I do that using react testing libraries?
My code:
it('sends back error callback', () => {
const isError = (val) => val;
render(<Component callback={isError}/>);
const input = screen.getAllByRole('textbox')[0];
fireEvent.change(input, {target: {value: '100.98'}});
expect(isError).toHaveBeenCalledTimes(1);
})
If you utilize a Mock (jest.fn()), you can simply use toHaveBeenCalledWith.
So in your case:
it("sends back error callback", () => {
const isError = jest.fn();
render(<Component callback={isError} />);
const input = screen.getAllByRole("textbox")[0];
fireEvent.change(input, { target: { value: "100.98" } });
expect(isError).toHaveBeenCalledWith(false);
});
Or use this alternative, especially if you have issue with the asynchronous nature of the code:
it("sends back error callback", (done) => {
const isError = (val) => {
expect(val).toBe(false);
done();
};
render(<Component callback={isError} />);
const input = screen.getAllByRole("textbox")[0];
fireEvent.change(input, { target: { value: "100.98" } });
});
Read more about async code: https://jestjs.io/docs/asynchronous.
If you have any questions to my answer/or the answer is not working, please get back to me in the comments 😊.

React Testing Library: Why does the order of the tests matter?

Here's a link to codesanbox
Reordering the below tests would make find one after the other test to pass.
Another way to make it pass would be to make should be able to find 3 directly test to pass, for example, by making it find 2 instead of 3.
describe("Counter", () => {
test("should be able to find 3 directly", async () => {
render(<Counter />);
const three = await waitFor(() => screen.findByText(/3/i));
expect(three).toBeInTheDocument();
});
test("find one after the other", async () => {
render(<Counter />);
const one = await waitFor(() => screen.findByText(/1/i));
expect(one).toBeInTheDocument();
const two = await waitFor(() => screen.findByText(/2/i));
expect(two).toBeInTheDocument();
const three = await waitFor(() => screen.findByText(/3/i));
expect(three).toBeInTheDocument();
});
});
So the question is, why does the order of the tests matter? Why is the clean up not working?
It looks like waitFor has reach its timeout as waiting for the counter to 3 which means it is unrelated to ordering in this case I guess.
You would fix by increase the timeout to wait your counter as following:
test("should be able to find 3 directly", async () => {
render(<Counter />);
const three = await waitFor(() => screen.findByText(/3/i), {
timeout: 3e3, // 3s wait would find 3
});
expect(three).toBeInTheDocument();
});

How to fire an event React Testing Library

I have some code, in a hook, to detect whether the browser is online / offline:
export function useConnectivity() {
const [isOnline, setNetwork] = useState(window.navigator.onLine);
const updateNetwork = () => {
setNetwork(window.navigator.onLine);
};
useEffect(() => {
window.addEventListener('offline', updateNetwork);
window.addEventListener('online', updateNetwork);
return () => {
window.removeEventListener('offline', updateNetwork);
window.removeEventListener('online', updateNetwork);
};
});
return isOnline;
}
I have this basic test:
test('hook should detect offline state', () => {
let internetState = jest.spyOn(window.navigator, 'onLine', 'get');
internetState.mockReturnValue(false);
const { result } = renderHook(() => useConnectivity());
expect(result.current.valueOf()).toBe(false);
});
However, I want to run a test to see whether it returns the correct value when an offline event is triggered, not just after the mocking of the returned value on render. What is the best way to approach this? Where I have got so far is this:
test('hook should detect offline state then online state', async () => {
const { result, waitForNextUpdate } = renderHook(() => useConnectivity());
act(() => {
const goOffline = new window.Event('offline');
window.dispatchEvent(goOffline);
});
await waitForNextUpdate();
expect(result.current).toBe(false);
});
I'm not sure about 'best', but this is one way: change the mock response halfway through the test, and tweak some of the async code:
test('hook should detect online state then offline state', async () => {
const onLineSpy = jest.spyOn(window.navigator, 'onLine', 'get');
// Pretend we're initially online:
onLineSpy.mockReturnValue(true);
const { result, waitForNextUpdate } = renderHook(() => useConnectivity());
await act(async () => {
const goOffline = new window.Event('offline');
// Pretend we're offline:
onLineSpy.mockReturnValue(false);
window.dispatchEvent(goOffline);
await waitForNextUpdate();
});
expect(result.current).toBe(false);
});

Resources