Testing React, hooks, snapshots, styled-components, react-router and sessionStorage with testing-library
Getting testing-library and jest work with my React code is quit time consuming, shared here to help people who is facing this kind of hard time. I’m trying to avoid long-wind and make things simple and followable. Feel free comment or ask questions. Happy coding!
Testing environment configuration
// setupTests.js /* eslint-disable */
import '@testing-library/react/dont-cleanup-after-each';
import '@testing-library/jest-dom/extend-expect';
import 'jest-styled-components';
import 'mock-local-storage'
import React from 'react'
import Adapter from 'enzyme-adapter-react-16'
import {configure, shallow, mount} from 'enzyme'
import {render} from '@testing-library/react';
import {ThemeConsumer, ThemeProvider} from "styled-components";
import {QueryClientProvider, QueryClient} from "react-query";
import {Router} from "react-router-dom";
import {createBrowserHistory} from "history";
import defaultTheme from "@utils/styles"; configure({adapter: new Adapter()})
for testing Ant Design
Object.defineProperty(window, 'matchMedia', { writable: true, value: jest.fn().mockImplementation(query => ({ matches: false, media: query, onchange: null, addListener: jest.fn(), removeListener: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), dispatchEvent: jest.fn(), })),
});
for testing sessionStorage
Object.defineProperty(window, 'sessionStorage', { writable: true, value: global.localStorage
})
for bypassing Suspense() and lazy() API when testing components
jest.mock('react', () => { const React = jest.requireActual('react'); React.Suspense = ({children}) => children; React.lazy = (factory) => factory() return React;
});
jest.useFakeTimers('modern'); // tell Jest to use a different timer implementation.
jest.setSystemTime(new Date('2222-12-22T22:22:22+08:00').getTime()); // Set a static time for snapshot
for testing styled-components with theme
export const shallowWithTheme = (children, theme = defaultTheme) => { ThemeConsumer._currentValue = theme; return shallow(children);
};
for testing styled-components and react-query
const defaultQueryClient = new QueryClient()
export const renderWithThemeAndQuery = (children, theme = defaultTheme, queryClient = defaultQueryClient) => { return render( <ThemeProvider theme={theme}> <QueryClientProvider client={queryClient}> {children} </QueryClientProvider>
</ThemeProvider>
)
};
for testing react-router-dom
const defaultHistory = createBrowserHistory();
export const renderWithRouter = (children, history = defaultHistory) => { return { ...renderWithThemeAndQuery(<Router history={history}>{children}</Router>),
history }
};
Test case examples
react-query hooks
import { QueryClient, QueryClientProvider } from 'react-query'; function updatePost(url, title, content) { return axios.put(url, { title, content });
} function useFetchAllPosts(query) { return useQuery( ['allPosts', query], () => fetchTestAppointments(query), { cacheTime: 3000, retry: 1, refetchOnWindowFocus: false, }, );
} // REF: https://react-query.tanstack.com/examples/optimistic-updates
function useSavePost(apiUrl) { const client = useQueryClient(); return useMutation( ['savePost', apiUrl], ({ url, title, content }) => useSavePost(url, title, content), { onSuccess(res) { client.refetchQueries('allPosts'); // reload all posts from backend }, }, );
} const defaultQueryClient = new QueryClient();
const wrapper = ({ children }) => ( <QueryClientProvider client={defaultQueryClient}> {children} </QueryClientProvider>
); describe('useSavePost', () => { const apiUrl = 'my url'; it('should has proper props', async () => { const { result, waitForNextUpdate } = renderHook(() => useSavePost(apiUrl), { wrapper }); result.current.mutate({ url, title, content }); await waitForNextUpdate(); expect(updatePost).toBeCalledWith(url, qu); });
});
Bonus
query-extensions - npm
https://www.npmjs.com/package/query-extensions
This package enhances testing-library with locating node with css selectors
Example:
screen.getBySelector(".company-logo"); screen.queryBySelector(".company-logo")