React Testing Library: Jak mockować requesty?
Opublikowano: 03.10.2023 - tagi: JavaScript React Testowanie Test Mock Request
Testowanie requestów
Pisanie testów dla funkcjonalności, które wykorzystują requesty może być irytujące. W zależności, jak taki test napiszesz.
Możesz mockować funkcje biblioteki, którą używasz do obsługi reqestów.
Albo przyjąć inne podejście: mockujesz tylko same requesty. To znaczy: jeśli w kodzie zostanie wywołany GET /api/user
, ustalasz, że ma zwrócić konkretne dane i tyle!
Na pierwszy rzut oka oba podejścia mogą wydawać się podobne, ale tak nie jest.
W pierwszym podejściu skupiasz się niepotrzebnie na szczegółach: mockujesz konkretną bibliotekę i metodę. Co jeśli w przyszłości będziesz chciał zmienić bibliotekę do obsługi requestów? Po zmianie testy oczywiście się załamią.
Znacznie lepszym rozwiązaniem jest mockowanie samych requestów. Dzięki temu skupiasz się na testowaniu tego, co w rzeczywistości jest ważne: na testowaniu funkcjonalności, a nie szczegółach implementacji
Więc jak mockować requesty?
Jest kilka gotowych bibliotek. W tym wpisie opiszę, jak działa Nock.
Nock
Jest to prosta biblioteka, która pozwala w banalny sposób mockować requesty.
Instalacja
Żeby zainstalować nock'a wywołaj komendę:
npm i nock -d
Jak korzystać z Nock?
Poniżej przykład mockowania request'a typu GET:
import nock from 'nock';
nock(host)
.get(endpoint)
.reply(200, response);
Gdzie odpowiednio:
- host — To adres do naszego API.
- endpoint — To, co chcemy zamokować.
- response — Możemy też określi, co zwróci endpoint po jego wywołaniu.
Przykładowa aplikacja
Do stestowania jest: wczytywanie listy zadań oraz zapis nowego zadania.
Do obsługi requestów w tym przykładzie korzystam z biblioteki axios.
Komponent:
import {useState, useEffect} from "react";
import axios from "axios";
interface Task {
id: number;
title: string;
};
export function MyComponent() {
const [tasks, setTasks] = useState<Task[]>([]);
const [task, setTask] = useState<string>('');
useEffect(() => {
axios.get('/api/task').then(res => setTasks(res.data));
}, []);
const onAddTask = () => {
axios.post('/api/task', { task }).then(res => {
const { id } = res.data;
setTask('');
setTasks([{ id, title: task }, ...tasks]);
})
}
return (
<>
<div>
<label htmlFor="task">Task title:</label>
<input name="task" id="task" value={task} onChange={(e) => setTask(e.target.value)} />
<button onClick={onAddTask}>Add</button>
</div>
<ul>
{
tasks.map((task: Task) => <li key={task.id}>{task.title}</li>)
}
</ul>
</>
)
}
Testowanie GET: wczytywanie listy
Test:
import {render, screen} from '@testing-library/react';
import nock from 'nock';
import {MyComponent} from "./MyComponent";
test('should display list of tasks', async () => {
// given
nock('http://localhost')
.get('/api/task')
.reply(200, [
{
id: 1,
title: "Task 1"
},
{
id: 2,
title: "Task 2"
},
{
id: 3,
title: "Task 3"
}
]);
// when
render(<MyComponent />)
// then
expect(
await findByText('Task 1')
).toBeInTheDocument();
expect(
await findByText('Task 2')
).toBeInTheDocument();
expect(
await findByText('Task 3')
).toBeInTheDocument();
});
Testowanie POST: zapis danych
Test:
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import nock from 'nock';
import {MyComponent} from "./MyComponent";
test('should add new task', async () => {
// given
nock('http://localhost')
.get('/api/task')
.reply(200, [
{
id: 1,
title: "Task 1"
},
{
id: 2,
title: "Task 2"
},
{
id: 3,
title: "Task 3"
}
])
.post('/api/task')
.reply(200, {
id: 4
});
render(<MyComponent />);
const taskInput = getByLabelText('Task title:');
await userEvent.type(taskInput, 'Task 4');
const taskAddBtn = getByRole('button', { name: 'Add' });
// when
await userEvent.click(taskAddBtn);
// then
expect(
await findByText('Task 4')
).toBeInTheDocument();
});