Setting Up Jest in a React TypeScript Workspace

Before you begin writing tests, you need to configure your workspace to support Jest in a React and TypeScript environment. Let’s walk through the steps required to get Jest up and running.

1. Installing Dependencies

The first step is to install the necessary dependencies. Jest and its TypeScript configuration require a few additional packages:


npm install --save-dev jest @types/jest ts-jest

Here’s a quick breakdown of the dependencies:

  • jest: The core Jest testing library.
  • @types/jest: TypeScript type definitions for Jest.
  • ts-jest: A TypeScript preprocessor for Jest that lets you use TypeScript with Jest.

2. Configuring Jest

After installing the dependencies, the next step is to configure Jest. Create a configuration file named jest.config.js at the root of your project:


module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  moduleNameMapper: {
    '\\.(css|scss)$': 'identity-obj-proxy',
  },
  setupFilesAfterEnv: ['/src/setupTests.ts'],
};

Let’s break down the configuration:

  • preset: Specifies the use of ts-jest to handle TypeScript files.
  • testEnvironment: Sets the environment to jsdom, which is a simulation of the browser environment and is ideal for testing React components.
  • moduleNameMapper: Handles the mapping of CSS and SCSS modules. Jest doesn’t understand these files by default, so we use identity-obj-proxy to mock them.
  • setupFilesAfterEnv: Indicates where to include any setup files or scripts that need to run before tests (e.g., for configuring test environments or global variables).

3. Setting Up a Test Environment

For React projects, you often need to configure additional testing utilities such as @testing-library/react. Install it using the following command:


npm install --save-dev @testing-library/react @testing-library/jest-dom

Now, in your src/setupTests.ts file, add the following imports to extend Jest’s functionality with additional matchers from jest-dom:


import '@testing-library/jest-dom';

This setup will allow you to write more intuitive assertions, such as checking if an element is in the document or has a specific class.

Writing Tests in a React TypeScript Workspace

Now that your workspace is set up, let’s move on to writing tests. Jest works seamlessly with TypeScript and React. Below are some examples of writing unit tests and component tests in a React TypeScript workspace.

1. Testing Pure Functions

Pure functions are easy to test because they don’t depend on external states. Here’s an example of a simple utility function and its test:


// utils/math.ts
export function add(a: number, b: number): number {
  return a + b;
}

// __tests__/math.test.ts
import { add } from '../utils/math';

test('adds two numbers correctly', () => {
  expect(add(1, 2)).toBe(3);
});

This test simply checks that the add function returns the correct result when given two numbers.

2. Testing React Components

Testing React components involves rendering them in isolation and asserting that they behave as expected. For this, we’ll use @testing-library/react.

Here’s an example of a simple React component:


// components/Greeting.tsx
import React from 'react';

interface GreetingProps {
  name: string;
}

const Greeting: React.FC = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

export default Greeting;

Now, let’s write a test for the Greeting component:


// __tests__/Greeting.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import Greeting from '../components/Greeting';

test('renders the correct greeting', () => {
  render(<Greeting name="John" />);
  
  const greetingElement = screen.getByText(/hello, john!/i);
  
  expect(greetingElement).toBeInTheDocument();
});

In this test, we use the render method from @testing-library/react to render the Greeting component. We then use screen.getByText to find the text that matches the greeting and assert that it is present in the document.

3. Mocking API Calls

In some cases, components depend on data fetched from an API. Jest provides a built-in way to mock API calls, allowing you to test components without actually making network requests.

Here’s an example of a component that fetches data from an API:


// components/UserList.tsx
import React, { useEffect, useState } from 'react';

interface User {
  id: number;
  name: string;
}

const UserList: React.FC = () => {
  const [users, setUsers] = useState<User[]>([]);

  useEffect(() => {
    async function fetchUsers() {
      const response = await fetch('/api/users');
      const data = await response.json();
      setUsers(data);
    }

    fetchUsers();
  }, []);

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

export default UserList;

We can mock the fetch API in our test like this:


// __tests__/UserList.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import UserList from '../components/UserList';

beforeEach(() => {
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve([{ id: 1, name: 'John Doe' }]),
    })
  );
});

test('displays a list of users', async () => {
  render(<UserList />);

  const userElement = await screen.findByText(/john doe/i);
  
  expect(userElement).toBeInTheDocument();
});

In this test, we mock the global fetch function to return a predefined response. The test then asserts that the component correctly renders the fetched data.

Best Practices for Writing Jest Tests

Here are some best practices to keep in mind when writing Jest tests in a React TypeScript workspace:

1. Keep Tests Small and Focused

Each test should focus on a single behavior or piece of functionality. This makes it easier to identify which part of the code is failing when tests break.

2. Use Descriptive Test Names

Test names should clearly describe the behavior being tested. This helps make your test suite more readable and easier to maintain.

3. Mock External Dependencies

Whenever your components rely on external services (e.g., API calls or third-party libraries), use mocks to isolate the component’s logic from the external service.

4. Write Tests for Edge Cases

It’s important to test not only the expected behavior but also edge cases and error scenarios. This ensures that your code handles all possible inputs gracefully.

5. Use Snapshot Testing

Jest’s snapshot testing feature is great for ensuring that your components’ output doesn’t change unexpectedly over time. Use snapshots to capture the rendered output of components and compare it with future renderings.

Conclusion

Jest is a powerful and flexible testing tool that integrates smoothly with a React TypeScript workspace. By setting up your project with Jest and following the best practices discussed in this article, you can ensure that your React components and utility functions are tested thoroughly and correctly. Testing not only improves code quality but also provides confidence that your application behaves as expected.