Test Next.js application with Jest (TypeScript)

Configuration

Install jest and other required packages:

npm install --save-dev jest @types/jest ts-jest jest-environment-jsdom @testing-library/react
npm install --save-dev babel-jest @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript

Create jest.config.js in the application root folder with the following content:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.(ts|tsx)$': 'babel-jest',
  },
};

Create .babelrc in the application root directory with the following content:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}

TypeScript testing

// src/apps/math.ts  
const add = (a: number, b: number): number => {
  return a + b;
};  
  
export { add };
// src/apps/math.test.ts
import { add } from ('./math');
 
test('adds 1 + 2 to equal 3', () => {  
  expect(add(1, 2)).toBe(3);  
});

Basic component testing

// src/app/Button.tsx
import React from 'react';
 
interface ButtonProps {
  children: React.ReactNode;
  onclick: () => void;
}
 
export default function Button({children, onclick}: ButtonProps) {
  return (
    <button>
      {children}
    </button>
  );
};
// src/app/Button.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import Button from './Button';
 
describe('Button', () => {
  it('renders a button', () => {
    render(<Button onclick={() => {}}>Click me</Button>);
    expect(screen.getByRole('button')).toBeInTheDocument();
  });
});

Snapshot testing

npm install --save-dev react-test-renderer @types/react-test-renderer
import React from 'react';
import renderer from 'react-test-renderer';
import Button from '../app/Button';
 
test('Button snapshot', () => {
  const component = renderer.create(<Button onclick={() => {}}>Click Me</Button>);
  const tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

User interaction testing

// src/app/LoginForm.tsx
import React, { useState } from 'react';
 
const LoginForm = () => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
 
  const handleSubmit = (event: React.FormEvent) => {
    event.preventDefault();
    // Handle form submission here
    console.log(`Username: ${username}, Password: ${password}`);
  };
 
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
          aria-label="Username"
        />
      </label>
      <label>
        Password:
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          aria-label="Password"
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};
 
export default LoginForm;
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import LoginForm from '../app/LoginForm';
 
test('submits the form when the submit button is clicked', () => {
  const { getByText, getByLabelText } = render(<LoginForm />);
  const usernameInput = getByLabelText('Username');
  const passwordInput = getByLabelText('Password');
  const submitButton = getByText('Submit');
  fireEvent.change(usernameInput, { target: { value: 'myusername' } });
  fireEvent.change(passwordInput, { target: { value: 'mypassword' } });
  fireEvent.click(submitButton);
 
  //
  // Add assertions to check the form submission behaviour
});

Testing API routes

supertest is a library that can be used to test HTTP requests.

npm install --save-dev supertest 

Testing HTTP POST and GET requests

import request from 'supertest';
import app from '../api/app';
 
test('GET /api/data returns expected data', async () => {  
  const response = await request(app).get('/api/data');
  expect(response.status).toBe(200);  
  expect(response.body).toEqual({ message: 'Hello, world!' });  
});

References