Goals
  1. render a React component in a test

  2. mock HTTP/REST calls to the backend

  3. verify results

  4. achieve the above using typescript, jest and testing-library

Application under Test

This is our application’s shortened package.json, generated by create-react-app, adding dependencies for …​

  • jest

  • testing-library

  • typescript

  • react

  • dom implementations (jest-dom/react-dom)

  • axios (for the HTTP/REST call)

package.json
{
  [..]
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "@types/jest": "^26.0.15",
    "@types/node": "^12.0.0",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "4.0.3",
    "typescript": "^4.1.2",
    "web-vitals": "^1.0.1",
    "axios": "^0.19.0"
  },
  [..]
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  [..]
}

In the next step we’re adding jest-dom to our test setup by adding the following setupTests.ts to our project if create-react-app has not already generated it:

setupTests.ts
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

This is our component to be tested …​ MyComponent.tsx:

It displays a simple greeting and when the button is clicked, it fetches messages via HTTP request and displays them in a list.

MyComponent.tsx
import {useCallback, useState} from 'react';
import {someRestClient} from './rest-client';

export const SampleComponent = () => {
    const [messages, setMessages] = useState<string[]>([]);

    const handleClick = useCallback(
        () => someRestClient.getSomething(
            (response) => setMessages(response.messages)
        ),
        []);


    return (<div>
        <h1>Hello!</h1>
        <ul>
            {messages.map(msg => <li key={msg} className='item'>{msg}</li>)}
        </ul>
        <button onClick={handleClick}>Show messages</button>
    </div>);
}

This is our REST client used above which initiates the HTTP call to the backend server using the popular Axios library.

rest-client.ts
import axios, {AxiosResponse} from 'axios';

export type MyRestResponse = {
    messages: string[]
}

export type MyRestService = {
    getSomething(callback: (r: MyRestResponse) => void): void
}

export const someRestClient: MyRestService = {
    async getSomething(callback) {
        return axios.create({
            baseURL: 'https://www.hascode.com/',
            timeout: 10000
        })
            .get('/fahrwege')
            .then((res: AxiosResponse<MyRestResponse>) => {
                callback(res.data);
            })
            .catch(err => {
                console.error("fail...", err);
            });
    }
};

Writing the Test

We’re creating a new file named MyComponent.spec.tsx:

MyComponent.spec.tsx
import {SampleComponent} from './SampleComponent';
import {render} from '@testing-library/react';
import {someRestClient} from './rest-client';

describe('SampleComponent', () => {
    it('should render list of messages fetched', () => {
        // fake REST client
        const spy = jest.spyOn(someRestClient, 'getSomething'); (1)
        spy.mockImplementation((c) => c({messages: ['hello', 'there']}));

        // render component
        const {container, queryByRole} = render(<SampleComponent/>); (2)

        /// verify header
        const heading = queryByRole('heading'); (3)
        expect(heading).toHaveTextContent('Hello!');

        // verify button
        const button = queryByRole('button'); (4)
        expect(button).toBeInTheDocument();
        expect(button).toHaveTextContent('Show messages');

        // click button
        button?.click(); (5)

        // verify list has been updated with results from REST query
        const items = container.querySelectorAll('li'); (6)
        expect(items.length).toBe(2);
        expect(items.item(0)).toHaveTextContent('hello');
        expect(items.item(1)).toHaveTextContent('there');
    })
});
1 mock the HTTP/REST client using Jest
2 render the component and desctructure some accessors that we need later
3 verify our heading exists and is correct
4 do the same for our button
5 click the button - this should lead to our test spy / mock called
6 verify that the items from the REST call are displayed in our list

Run the Tests

We may now run the tests in our IDE of choice or the command line with yarn test:

Running the test
$ yarn test
yarn run v1.22.10
$ react-scripts test
 PASS  src/SampleComponent.spec.tsx
  SampleComponent
    √ should render list of messages fetched (82 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.337 s
Ran all test suites related to changed files.