Jest is a library for unit testing, tests of app units without external dependencies. Installation Jest for Next packages npm install --save-dev jest @testing-library/react @testing-library/jest-dom Configuration // jest.config.js const nextJest = require('next/jest') const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment dir: './', }) // Add any custom config to be passed to Jest const customJestConfig = { // Add more setup options before each test is run // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work moduleDirectories: ['node_modules', '/'], testEnvironment: 'jest-environment-jsdom', } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async module.exports = createJestConfig(customJestConfig) // package.json "scripts": { "dev": "node exportAllPostsCreate && next dev", "build": "node exportAllPostsCreate && next build", "start": "next start", "lint": "next lint", "test": "jest --watch" }, // .eslintrc.js module.exports = { env: { browser: true, es2021: true, jest: true }, extends: [ 'plugin:react/recommended', 'standard' ], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 'latest', sourceType: 'module' }, plugins: [ 'react', '@typescript-eslint' ], rules: { 'react/react-in-jsx-scope': 'off', 'space-before-function-paren': 'off', 'react/prop-types': 'off', 'import/no-absolute-path': 'off' } } Run npm run test All assertions toHaveBeenCalledTimes , toHaveBeenCalledWith , toHaveBeenLastCalledWith , toHaveBeenNthCalledWith , toHaveClass , toHaveDisplayValue , toHaveErrorMessage , toHaveFocus , toHaveFormValues , toHaveLastReturnedWith , toHaveLength , toHaveNthReturnedWith , toHaveProperty , toHaveReturned , toHaveReturnedTimes , toHaveReturnedWith , toHaveStyle , toHaveTextContent , toHaveValue , toMatch , toMatchInlineSnapshot , toMatchObject , toMatchSnapshot , toReturn , toReturnTimes , toReturnWith , toStrictEqual , toThrow , toThrowError , toThrowErrorMatchingInlineSnapshot , toThrowErrorMatchingSnapshot , lastCalledWith , lastReturnedWith , not , nthCalledWith , nthReturnedWith , rejects , resolves , toBe , toBeCalled , toBeCalledTimes , toBeCalledWith , toBeChecked , toBeCloseTo , toBeDefined , toBeDisabled , toBeEmptyDOMElement , toBeEnabled , toBeFalsy , toBeGreaterThan , toBeGreaterThanOrEqual , toBeInTheDocument , toBeInstanceof , toBeInvalid , toBeLessThan , toBeLessThanOrEqual , toBeNaN , toBeNull , toBePartiallyChecked , toBeRequired , toBeTruthy , toBeUndefined , toBeValid , toBeVisible , toContain , toContainElement , toContainEqual , toContainHTML , toEqual , toHaveAccessibleDescription , toHaveAccessibleName , toHaveAttribute , toHaveBeenCalled , toHaveBeenCalledTimes , toHaveBeenCalledWith , toHaveBeenLastCalledWith Main assertions toBe Compares references in memory. test('exact equality', () => { expect(2 + 2).toBe(4) // true }) not.toBe test('exact equality for objects', () => { expect({ a: 1 }).not.toBe({ a: 1 }) // false }) toEqual Compares all values test('values equality', () => { expect({ a: 1 }).toEqual({ a: 1 }) // true }) toMatchObject test('partial equality', () => { expect({ a: 1, b: 2 }).toMatchObject({ a: 1 }) }) toHaveProperty test('toHaveProperty', () => { expect({ a: 1, b: 2 }).toHaveProperty('a', 1) }) toBeNull test('null', () => { const n = null expect(n).toBeNull() }) toBeDefined test('null', () => { const n = null expect(n).toBeDefined() }) toBeUndefined test('null', () => { const n = null expect(n).not.toBeUndefined() }) toBeTruthy test('null', () => { const n = null expect(n).not.toBeTruthy() }) toBeFalsy test('null', () => { const n = null expect(n)toBeFalsy() }) toBeGreaterThan test('two plus two', () => { const value = 2 + 2 expect(value).toBeGreaterThan(3) }) toBeGreaterThanOrEqual test('two plus two', () => { const value = 2 + 2 expect(value).toBeGreaterThanOrEqual(3.5) }) toBeLessThan test('two plus two', () => { const value = 2 + 2 expect(value).toBeLessThan(5) }) toBeLessThanOrEqual test('two plus two', () => { const value = 2 + 2 expect(value).toBeLessThanOrEqual(4.5) }) toBeCloseTo test('adding floating point numbers', () => { const value = 0.1 + 0.2 expect(value).toBeCloseTo(0.3) }) toMatch test('hi', () => { expect('team').not.toMatch('hi') }) test('there is no I in team', () => { expect('team').not.toMatch(/I/) }) test('but there is a "stop" in Christoph', () => { expect('Christoph').toMatch(/stop/) }) toContain test('arr contains', () => { expect(['1', '2', '3', '4', '5']).toContain('2') }) arrayContaining test('arr contains', () => { expect(['1', '2', '3', '4', '5']).toEqual(expect.arrayContaining(['1', '2'])) }) toThrow function functionWithError() { throw new Error('very bad error') } test('compiling android goes as expected', () => { expect(() => functionWithError()).toThrow() expect(() => functionWithError()).toThrow(Error) expect(() => functionWithError()).toThrow('very bad error') expect(() => functionWithError()).toThrow(/bad/) }) Promises test('get userId from json api', () => { return axios('https://jsonplaceholder.typicode.com/posts/1') .then(res => { expect(res.data.userId).toBe(1) }) }) test('get userId from json api with async await', async () => { const res = await axios('https://jsonplaceholder.typicode.com/posts/1') expect(res.data.userId).toBe(1) }) resolves test('promise resolves', async () => { function promiseWithResolve() { return new Promise((resolve, reject) => { setTimeout(() => resolve('done'), 500) }) } await expect(promiseWithResolve()).resolves.toEqual('done') }) rejects test('promise rejects', async () => { function promiseWithResolve() { return new Promise((resolve, reject) => { setTimeout(() => reject('error'), 500) }) } await expect(promiseWithResolve()).rejects.toEqual('error') }) beforeEach & afterEach Calls function for every test in a file or for a describe block. beforeEach(() => { console.log('test starts') }) afterEach(() => { console.log('test ended') }) beforeAll & afterAll Calls function before and after all tests in a file or for a describe block. beforeAll(() => { console.log('tests start') }) afterAll(() => { console.log('tests ended') }) describe Group tests in describe block beforeEach , afterEach , beforeAll , afterAll inside describe block affect tests inside this block. describe('test function name', () => { beforeEach(() => { console.log('starts before each function in describe block') }) test('to be one', () => { expect(1).toBe(1) }) test('to be two', () => { expect(2).toBe(2) }) }) Mock function basics Read here . test('mock function basics', () => { const mockFn = jest.fn() mockFn() mockFn('arg1', 'arg2') expect(mockFn).toBeCalled() expect(mockFn).toBeCalledTimes(2) expect(mockFn.mock.calls.length).toBe(2) expect(mockFn).toBeCalledWith('arg1', 'arg2') // last call console.log('mockFn.mock.calls', mockFn.mock.calls) // [ [], [ 'arg1', 'arg2' ] ] expect(mockFn.mock.calls[1][0]).toBe('arg1') expect(mockFn.mock.calls[1][1]).toBe('arg2') }) afterAll(() => { jest.clearAllMocks() }) Mock function with return value test('mock function with return value', () => { const mockFn = jest.fn() mockFn() mockFn.mockReturnValue('hi') expect(mockFn()).toBe('hi') }) Mock function with resolve value test('mock function with resolve value', async () => { const mockFn = jest.fn() mockFn() mockFn.mockResolvedValue('hi') expect(await mockFn()).toBe('hi') console.log('mockFn.mock.results', mockFn.mock.results) // [ { type: 'return', value: undefined }, { type: 'return', value: 'hi' } ] }) Mock with custom implementation test('mock function with implementation', () => { const mockFn = jest.fn() mockFn.mockImplementation(arg => { if (typeof arg === 'string') return arg if (typeof arg === 'number') return 10 * arg }) expect(mockFn('hi')).toBe('hi') expect(mockFn(3)).toBe(30) // shorthand const mockFn2 = jest.fn(arg => 'hi') expect(mockFn2('hi')).toBe('hi') }) Mock function from other file // foo-bar-baz.js export const foo = 'foo' export const bar = () => 'bar' export default () => 'baz' //test.js import defaultExport, {bar, foo} from '../foo-bar-baz' jest.mock('../foo-bar-baz', () => { const originalModule = jest.requireActual('../foo-bar-baz') //Mock the default export and named export 'foo' return { __esModule: true, ...originalModule, default: jest.fn(() => 'mocked baz'), foo: 'mocked foo', } }) test('should do a partial mock', () => { const defaultExportResult = defaultExport() expect(defaultExportResult).toBe('mocked baz') expect(defaultExport).toHaveBeenCalled() expect(foo).toBe('mocked foo') expect(bar()).toBe('bar') }) Mock Redux jest.mock('react-redux', () => { const originalModule = jest.requireActual('react-redux') return { __esModule: true, ...originalModule, useSelector: jest.fn().mockReturnValue({ title: 'some title', message: 'some message', isOpen: true }), useDispatch: () => jest.fn() } }) spyOn Same as previous, but a bit different way. test('spyon', async () => { const obj = { fetchPost: function () { return axios('https://jsonplaceholder.typicode.com/posts/1') .then(res => res.data) } } console.log(await obj.fetchPost()) // { userId: 1, id: 1, title: 'sunt' } // instead of calling real API we can spy on the function and replace its behavior jest.spyOn(obj, 'fetchPost') .mockImplementation(() => Promise.resolve('data is fetched, but it is not your business')) console.log(await obj.fetchPost()) // data is fetched, but it is not your business }) afterEach(() => { jest.restoreAllMocks() }) Mock function return different values on different calls test('mock function return different values', () => { const mockFn = jest .fn() .mockReturnValue('default') .mockReturnValueOnce('hi') .mockReturnValueOnce('bye') expect(mockFn()).toBe('hi') expect(mockFn()).toBe('bye') expect(mockFn()).toBe('default') }) Mock Mock the external library. test('should mock the lib', () => { jest.mock('shortid', () => { return jest.fn(() => '23kDr6') }) const id = require('shortid') console.log(id()) // '23kDr6' }) Run one test only temporarily change that test command to a test.only . test.only('this will be the only test that runs', () => { expect(true).toBe(false) }) test('this test will not run', () => { expect('A').toBe('A') }) it Same as test , shorter and makes description sound like normal language. it('should be 2', () => { expect(1 + 1).toBe(2) }) Suppress jest warning describe('<Invoices />', () => { it('should render the component', () => { jest.spyOn(console, 'error').mockImplementation(() => {}) renderWithProvider(<Invoices />) const invoices = screen.getByTestId('invoices') expect(invoices).toBeInTheDocument() }) }) Mock imported function I am testing a component which uses react-query functions to fetch a data on mount In test we do not want to fetch the data and need to mock it Component acts always the same no matter useUserQuery() returns So we need to mock it ones for all tests Component renders differently depending on useInvoicesQuery() return value Here we need to control the return value export const InvoicesTable = () => { const dispatch = useDispatch() const { data: user } = useUserQuery() const { data, isLoading } = useInvoicesQuery() const isServiceCenter = selectIsServiceCenter(user) return ( .... ) } import { screen } from '@testing-library/react' import { renderWithProvider } from 'testUtils/renderWithProvider' import { getDefaultStore } from 'testUtils/defaultStore' import { InvoicesTable } from './InvoicesTable' import { useInvoicesQuery } from 'api/useInvoicesQuery' const store = getDefaultStore() store.query.searchInputValue = 'some search input value' // returned value will be static in all test jest.mock('api/useUserQuery', () => ({ useUserQuery: () => ({ data: { username: 'Jack Russell' } }) })) // returned value can be controlled in different tests jest.mock('api/useInvoicesQuery', () => ({ useInvoicesQuery: jest.fn() })) describe('<InvoicesTable />', () => { it('should render tables content', () => { useInvoicesQuery.mockReturnValue({ isLoading: false, data: {} }) renderWithProvider(<InvoicesTable />, {}, { preloadedState: store }) expect(screen.queryByTestId('spinner')).not.toBeInTheDocument() expect(screen.getByText('14:27:21', { exact: false })).toBeInTheDocument() }) }) describe('<InvoicesTable />', () => { it('should show spinner', () => { useInvoicesQuery.mockReturnValue({ isLoading: true, data: {} }) renderWithProvider(<InvoicesTable />, {}, { preloadedState: store }) expect(screen.queryByTestId('spinner')).toBeInTheDocument() }) })