I have this custom hook:
import { useEffect } from 'react'
const useBeforeUnload = () => {
useEffect(() => {
const handleBeforeUnload = ev => {
console.log('Test')
}
window.addEventListener('beforeunload', handleBeforeUnload)
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
}, [])
}
export default useBeforeUnload
and I'm trying to get a simple test to work that checks to see if window.addEventListener is called:
import { renderHook } from '#testing-library/react-hooks'
import useBeforeUnload from './useBeforeUnload'
const spy = jest.fn()
delete window.addEventListener
window.addEventListener = spy
describe('useBeforeUnload', () => {
describe('When the hook is initialised', () => {
beforeEach(() => {
renderHook(() => useBeforeUnload())
})
test('It should register the correct event listener', () => {
expect(spy).toHaveBeenCalledTimes(1)
})
})
})
but it always fails saying that the listener was called 6 times???
This is because react-dom is also adding event handlers (for the error event) and these are the handlers that increase your number of calls
One thing you could do is to assert against what you want it to add rather than how many times
expect(spy).toHaveBeenCalledWith("beforeunload",expect.anything());
Related
I want to test a React component that, internally, uses a custom hook (Jest used). I successfully mock this hook but I can't find a way to test the calls on the functions that this hook returns.
Mocked hook
const useAutocomplete = () => {
return {
setQuery: () => {}
}
}
React component
import useAutocomplete from "#/hooks/useAutocomplete";
const MyComponent = () => {
const { setQuery } = useAutocomplete();
useEffect(() => {
setQuery({});
}, [])
...
}
Test
jest.mock("#/hooks/useAutocomplete");
it("sets the query with an empty object", () => {
render(<MyComponent />);
// I want to check the calls to setQuery here
// e.g. mockedSetQuery.mock.calls
});
CURRENT SOLUTION
I currently made the useAutocomplete hook an external dependency:
import useAutocomplete from "#/hooks/useAutocomplete";
const MyComponent = ({ autocompleteHook }) => {
const { setQuery } = autocompleteHook();
useEffect(() => {
setQuery({});
}, [])
...
}
MyConsole.defaultProps = {
autocompleteHook: useAutocomplete
}
And then I test like this:
const mockedSetQuery = jest.fn(() => {});
const useAutocomplete = () => ({
setQuery: mockedSetQuery,
});
it("Has access to mockedSetQuery", () => {
render(<MyComponent autocompleteHook={useAutocomplete} />);
// Do something
expect(mockedSetQuery.mock.calls.length).toBe(1);
})
You can mock the useAutocomplete's setQuery method to validate if it's invoked.
jest.mock("#/hooks/useAutocomplete");
it("sets the query with an empty object", () => {
const useAutocompleteMock = jest.requireMock("#/hooks/useAutocomplete");
const setQueryMock = jest.fn();
useAutocompleteMock.setQuery = setQueryMock;
render(<MyComponent />);
// The mock function is called twice
expect(setQueryMock.mock.calls.length).toBe(1);
});
I need to test the following component that consumes a custom hook of mine.
import { useMyHook } from 'hooks/useMyHook';
const MyComponent = () => {
const myHookObj = useMyHook();
const handler = () => {
myHookObj.myMethod(someValue)
}
return(
<button onClick={handler}>MyButton</button>
);
};
This is my test file:
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: () => {
return {
myMethod: jest.fn(),
};
},
};
});
describe('<MyComponent />', () => {
it('calls the hook method when button is clicked', async () => {
render(<MyComponent {...props} />);
const button = screen.getByText('MyButton');
userEvent.click(button);
// Here I need to check that the `useMyHook.method`
// was called with some `value`
// How can I do this?
});
});
I need to check that the useMyHook.method was called with some value.
I also want to test it from multiple it cases and it might be called with different values on each test.
How can I do this?
This is how I was able to do it:
import { useMyHook } from 'hooks/useMyHook';
// Mock custom hook that it's used by the component
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: jest.fn(),
};
});
// Mock the implementation of the `myMethod` method of the hook
// that is used by the Component
const myMethod = jest.fn();
(useMyHook as ReturnType<typeof jest.fn>).mockImplementation(() => {
return {
myMethod: myMethod,
};
});
// Reset mock state before each test
// Note: is needs to reset the mock call count
beforeEach(() => {
myMethod.mockReset();
});
Then, on the it clauses, I'm able to:
it (`does whatever`, async () => {
expect(myMethod).toHaveBeenCalledTimes(1);
expect(myMethod).toHaveBeenLastCalledWith(someValue);
});
I want to JEST test a React Component which updates state after a timeout both before and after rendering state. The expect() assertion works fine in a test() but breaks when used inside a SetTimeout callback. An error states that expect is not defined within the setTimeout callback.
I have created a sandbox here:
https://codesandbox.io/s/jest-delayed-render-test-j5b7l
Simple component update state after SetTimeout.
// delayed.js
import { useEffect, useState } from "react";
export const Delayed = () => {
let [value, setValue] = useState("foo");
useEffect(() => {
let handle = setTimeout(() => {
setValue("bar");
}, 3000);
return () => {
clearTimeout(handle);
};
}, []);
return (
<>
<h1>MyPromise</h1>
<p>value = {value}</p>
</>
);
};
Simple test asserts the component before and update timeout
// delayed.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import { Delayed } from "./delayed";
let container = null;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("checks initial value", async () => {
await act(async () => {
render(<Delayed />, container);
});
expect(container.textContent).toContain("foo");
});
it("checks final value", async () => {
await act(async () => {
render(<Delayed />, container);
});
setTimeout(() => {
// this doesnt get executed!
// error: expect is not defined
expect(container.textContent).toContain("1234");
}, 2000);
});
this is easily fixed passing in done as a param on the test.
it("checks final value", async (done) => {
await act(async () => {
render(<Delayed />, container);
});
setTimeout(() => {
expect(container.textContent).toContain("bar");
done();
}, 3000);
});
as described here:
https://www.pluralsight.com/guides/test-asynchronous-code-jest
I'm trying to test a simple hook i've made for intercepting offline/online events:
import { useEffect } from 'react';
const useOfflineDetection = (
setOffline: (isOffline: boolean) => void
): void => {
useEffect(() => {
window.addEventListener('offline', () => setOffline(true));
window.addEventListener('online', () => setOffline(false));
return () => {
window.removeEventListener('offline', () => setOffline(true));
window.removeEventListener('online', () => setOffline(false));
};
}, []);
};
export default useOfflineDetection;
------------------------------------
//...somewhere else in the code
useOfflineDetection((isOffline: boolean) => Do something with 'isOffline');
But I'm not sure I'm using the correct way to return value and moreover I'm not sure to get how to test it with jest, #testing-library & #testing-library/react-hooks.
I missunderstand how to mount my hook and then catch the return provide by callback.
Is someone can help me ? I'm stuck with it :'(
Thanks in advance!
EDIT:
Like Estus Flask said, I can use useEffect instead callback like I design it first.
import { useEffect, useState } from 'react';
const useOfflineDetection = (): boolean => {
const [isOffline, setIsOffline] = useState<boolean>(false);
useEffect(() => {
window.addEventListener('offline', () => setIsOffline(true));
window.addEventListener('online', () => setIsOffline(false));
return () => {
window.removeEventListener('offline', () => setIsOffline(true));
window.removeEventListener('online', () => setIsOffline(false));
};
}, []);
return isOffline;
};
export default useOfflineDetection;
------------------------------------
//...somewhere else in the code
const isOffline = useOfflineDetection();
Do something with 'isOffline'
But if I want to use this hook in order to store "isOffline" with something like redux or other, the only pattern I see it's using useEffect:
const isOffline = useOfflineDetection();
useEffect(() => {
dispatch(setIsOffline(isOffline));
}, [isOffline])
instead of just:
useOfflineDetection(isOffline => dispatch(setIsOffline(isOffline)));
But is it that bad ?
The problem with the hook is that clean up will fail because addEventListener and removeEventListener callbacks are different. They should be provided with the same functions:
const setOfflineTrue = useCallback(() => setOffline(true), []);
const setOfflineFalse = useCallback(() => setOffline(false), []);
useEffect(() => {
window.addEventListener('offline', setOfflineTrue);
...
Then React Hooks Testing Library can be used to test a hook.
Since DOM event targets have determined behaviour that is supported by Jest DOM to some extent, respective events can be dispatched to test a callback:
const mockSetOffline = jest.fn();
const wrapper = renderHook(() => useOfflineDetection(mockSetOffline));
expect(mockSetOffline).not.toBeCalled();
// called only on events
window.dispatchEvent(new Event('offline'));
expect(mockSetOffline).toBeCalledTimes(1);
expect(mockSetOffline).lastCalledWith(false);
window.dispatchEvent(new Event('online'));
expect(mockSetOffline).toBeCalledTimes(2);
expect(mockSetOffline).lastCalledWith(true);
// listener is registered once
wrapper.rerender();
expect(mockSetOffline).toBeCalledTimes(2);
window.dispatchEvent(new Event('offline'));
expect(mockSetOffline).toBeCalledTimes(3);
expect(mockSetOffline).lastCalledWith(false);
window.dispatchEvent(new Event('online'));
expect(mockSetOffline).toBeCalledTimes(4);
expect(mockSetOffline).lastCalledWith(true);
// cleanup is done correctly
window.dispatchEvent(new Event('offline'));
window.dispatchEvent(new Event('online'));
expect(mockSetOffline).toBeCalledTimes(4);
I'm using react-testing-library and jest to test if my API is not invoked when a certain prop is set. Currently the test succeeds immediately without finishing the useEffect() call. How do I make the test wait until useEffect has finished, so I can be certain the API has not been called?
The code:
const MyComponent = ({ dontCallApi }) => {
React.useEffect(() => {
const asyncFunction = async () => {
if (dontCallApi) {
return
}
await callApi()
}
asyncFunction
}, [])
return <h1>Hi!</h1>
}
it('should not call api when dontCallApi is set', async () => {
const apiSpy = jest.spyOn(api, 'callApi')
render(<MyComponent dontCallApi />)
expect(apiSpy).not.toHaveBeenCalled()
})
In your case, you could spy on React.useEffect and provide an alternative implementation. jest.spyOn(React, "useEffect").mockImplementation((f) => f())
so now you dont't have to care about the handling of useEffect anymore.
If you also want to test useEffect in a descent way you may extract the logic in a custom hook and use the testing library for hooks with the renderHooks function to test your use case.
I would test your Component like this:
import React from "react";
import { MyComponent } from "./Example";
import { render } from "#testing-library/react";
import { mocked } from "ts-jest/utils";
jest.mock("./api", () => ({
callApi: jest.fn(),
}));
import api from "./api";
const mockApi = mocked(api);
jest.spyOn(React, "useEffect").mockImplementation((f) => f());
describe("MyComponet", () => {
afterEach(() => {
jest.clearAllMocks();
});
it("should not call api when dontCallApi is set", async () => {
render(<MyComponent dontCallApi />);
expect(mockApi.callApi).toHaveBeenCalledTimes(0);
});
it("should call api when is not set", async () => {
render(<MyComponent />);
expect(mockApi.callApi).toHaveBeenCalledTimes(1);
});
});
Edit 03.07.2020
I recently found out that there is a possibility to query something like you wanted without mocking useEffect. You could simply use the async utilities of react testing library and get the following:
import React from "react";
import { MyComponent } from "./TestComponent";
import { render, waitFor } from "#testing-library/react";
import { api } from "./api";
const callApiSpy = jest.spyOn(api, "callApi");
beforeEach(() => {
callApiSpy.mockImplementation(() => Promise.resolve());
});
afterEach(() => {
callApiSpy.mockClear();
});
describe("MyComponet", () => {
afterEach(() => {
jest.clearAllMocks();
});
it("should not call api when dontCallApi is set", async () => {
render(<MyComponent dontCallApi />);
await waitFor(() => expect(callApiSpy).toHaveBeenCalledTimes(0));
});
it("should call api when is not set", async () => {
render(<MyComponent />);
await waitFor(() => expect(callApiSpy).toHaveBeenCalledTimes(1));
});
});
to get more information about this take a look at the async utilities docs