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');
})
});
-
mock the HTTP/REST client using Jest
-
render the component and desctructure some accessors that we need later
-
verify our heading exists and is correct
-
do the same for our button
-
click the button - this should lead to our test spy / mock called
-
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.