I created a custom hook to force a component to update but I'm having issues figuring out how to write a unit test with jest.
This is the hook
function useForceUpdate(condition) {
const [, setState] = useState(0);
const forceUpdate = () => setState(1);
useEffect(() => {
if (condition) {
forceUpdate();
}
}, [condition]);
}
export default useForceUpdate;
I was able to successfully test this hook this way
import React from "react";
import useForceUpdate from "hooks/use-force-update";
const Component = ({ shouldUpdate }) => {
const hasUpdated = useForceUpdate(shouldUpdate);
return <div>{hasUpdated}</div>;
};
describe("useForceUpdate", () => {
let subject;
let props;
beforeEach(() => {
props = { shouldUpdate: true };
subject = memoize(() => mount(<Component {...props} />));
});
describe("when the condition is true", () => {
it("it calls forceUpdate", () => {
expect(
subject()
.find("div")
.text()
).toBe("1");
});
});
describe("when the condition is false", () => {
beforeEach(() => {
props = { shouldUpdate: false };
});
it("it does not call forceUpdate", () => {
expect(
subject()
.find("div")
.text()
).toBe("0");
});
});
});
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);
});
export const AppLayout: React.FunctionComponent = React.memo(({ children }) => {
// Application main layout component name
AppLayout.displayName = getComponentName('App-Layout');
const { isAuthenticated } = useAuth();
const { sendRequest } = useApiService();
React.useEffect(() => {
const fetchData = async () => {
try {
...
} catch (err) {
console.error(err);
}
};
isAuthenticated() && fetchData();
}, []);
describe('App General component', () => {
const useAuth = jest.fn();
const useApiService = jest.fn();
const isAuthenticated = true;
const props = {};
const renderComponent = () => render(
<AppLayout/>
);
it('should render without errors', () => {
renderComponent();
});
/**
* Validate current user exist in session
* #returns {boolean}
*/
const isAuthenticated = React.useCallback((): boolean => {
return Boolean(user);
}, [user]);
How can I set isAuthenticated to true so I can avoid the error
TypeError: Cannot destructure property 'isAuthenticated' of
const mockUseAuthIsAuthenticated = jest.fn(() => false);
const mockUseAuth = jest.fn(() => ({
isAuthenticated: mockUseAuthIsAuthenticated,
});
jest.mock("../hooks/useAuth", mockUseAuth);
describe('My test case', () => {
it(`should return authenticated=TRUE`, () => {
// Given
mockUseAuthIsAuthenticated.mockImplementationOnce(
() => true
);
// When
// assuming `render` comes from the react testing-library
render(<ComponentThatCallsTheHook />);
// Then
expect(mockUseAuthIsAuthenticated).toHaveBeenCalledOnce();
// ... more expectations
});
});
You should mock the useAuth hook like this:
jest.mock("yourUseAuthPath", () => ({
useAuth: () => ({
isAuthenticated: () => true
}),
}));
describe('App General component', () => {
...
}
n.b. You should replace the yourUseAuthPath with the correct path where you get the useAuth from. Example:
import { useAuth } from "yourUseAuthPath";
Some official docs here: https://jestjs.io/docs/mock-functions#mocking-partials
I am only starting with unit testing now and the course I am following has the following syntax for a test:
expect(app.state().gifts).toEqual([])
This is the syntax for the use of class components but that will be deprecated soon so I am using React function components instead.
How do you accomplish the same test with hooks?
Thanks
You can use react-hooks-testing-library and test your hooks as well. Basic Hooks
Example :
useDisclosure.ts
import * as React from 'react';
export const useDisclosure = (initial = false) => {
const [isOpen, setIsOpen] = React.useState(initial);
const open = React.useCallback(() => setIsOpen(true), []);
const close = React.useCallback(() => setIsOpen(false), []);
const toggle = React.useCallback(() => setIsOpen((state) => !state), []);
return { isOpen, open, close, toggle };
};
useDisclosure.test.ts
import { renderHook, act } from '#testing-library/react-hooks';
import { useDisclosure } from '../useDisclosure';
test('should open the state', () => {
const { result } = renderHook(() => useDisclosure());
expect(result.current.isOpen).toBe(false);
act(() => {
result.current.open();
});
expect(result.current.isOpen).toBe(true);
});
test('should close the state', () => {
const { result } = renderHook(() => useDisclosure());
expect(result.current.isOpen).toBe(false);
act(() => {
result.current.close();
});
expect(result.current.isOpen).toBe(false);
});
test('should toggle the state', () => {
const { result } = renderHook(() => useDisclosure());
expect(result.current.isOpen).toBe(false);
act(() => {
result.current.toggle();
});
expect(result.current.isOpen).toBe(true);
act(() => {
result.current.toggle();
});
expect(result.current.isOpen).toBe(false);
});
test('should define initial state', () => {
const { result } = renderHook(() => useDisclosure(true));
expect(result.current.isOpen).toBe(true);
act(() => {
result.current.toggle();
});
expect(result.current.isOpen).toBe(false);
});
I have this hook that should trigger beforeunload event when the compoenent is mounted and unmounted.
const UseHook = (fns: (e) => void) => {
const cb = useRef(fns);
useEffect(() => {
cb.current = fn;
}, [fn]);
useEffect(() => {
const onUnloadFN = (args: BeforeUnloadEvent) => cb.current?.(args);
window.addEventListener('beforeunload', onUnloadFN);
return () => {
window.removeEventListener('beforeunload', onUnloadFN);
};
}, []);
};
Now I want to test the hook using jest and enzyme:
import { mount } from 'enzyme';
import React from 'react';
const HookWrapper = () => {
useHook((e) => {
e.preventDefault();
e.returnValue = '';
});
return <div>component</div>;
};
describe('useHook', () => {
const location: Location = window.location;
delete window.location;
const mockPageReloading = jest.fn();
window.location = {
...location,
reload: mockPageReloading,
};
it('should mount', () => {
const mockedOnload = jest.fn();
window.addEventListener = jest.fn((event) => {
if (event === 'beforeunload') {
mockedOnload();
}
});
const wrapper = mount(<HookWrapper />);
expect(mockedOnload).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
console.log(wrapper.debug());
});
it('should unmount', () => {
const mockedOnload = jest.fn();
window.removeEventListener = jest.fn((event) => {
if (event === 'beforeunload') {
mockedOnload();
}
});
const wrapper = mount(<HookWrapper />);
wrapper.unmount();
expect(mockedOnload).toHaveBeenCalledTimes(1);
});
});
The first test pass, but the second retrieve that the event listener wasn't call on unmount (it was called 0 times).
Who can help with this?
Basically I want to test if the event was triggered on mount and also on unmount.
PS: this hook is also used to detect when user reload the page. If somebody has other idea how to test this hook, please let me know.
I have this stateless React component:
...
const Providers = ({ onSelectFeedProvider, ... }) => {
const handleSelectFeedProvider = value => e => {
e.preventDefault();
onSelectFeedProvider({ target: { value } });
};
return {
<Row onClick={handleSelectFeedProvider(1)}>
...
</Row>
}
}
And the test:
import Row from 'components/Common/Row';
import Providers from './index';
jest.mock('components/Common/Row', () => 'Row');
let onSelectFeedProviderSpy = jest.fn();
let onSelectProviderSpy = jest.fn();
const initialProps = {
feedProvider: 0,
onSelectFeedProvider: () => onSelectFeedProviderSpy(),
selectedProvider: undefined,
onSelectProvider: () => onSelectProviderSpy()
};
const mockComponent = props => {
const finalProps = { ...initialProps, ...props };
return <Providers {...finalProps} />;
};
it('should call correctly', () => {
const wrapper = shallow(mockComponent());
wrapper.find(Row).simulate('click', 'what do I have to do here');
expect(onSelect).toHaveBeenCalledTimes(1);
});
How can I do to call the method correctly and pass the coverage? I think have tried all the possibilities. Any thoughts?
You don't have many options in this, one approach is to have onSelect injectable
const Component = ({onSelect}) => {
const handleSelect = value => e => {
e.preventDefault()
onSelect && onSelect({ target: { value } })
}
return <Row onClick={handleSelect(1)} />
}
Test
it('should call correctly', () => {
const spy = jest.fn()
const wrapper = shallow(mockComponent({onSelectProvider: spy}));
wrapper.find(Row).simulate('click', 'what do I have to do here');
expect(spy).toHaveBeenCalledTimes(1);
});