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)
{
[..]
"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:
// 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.
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.
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
:
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
:
$ 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.