RTK Query тесно взаимодействует с концепцией тестирования сетевого слоя, поскольку вся модель работы строится вокруг предсказуемых запросов, кэширования и автоматической синхронизации состояния. В этом контексте Mock Service Worker (MSW) становится ключевым инструментом для изоляции логики запросов от реального бэкенда и построения воспроизводимых сценариев.
MSW перехватывает запросы на уровне сетевого слоя, не требуя подмены
fetch или внедрения заглушек в код. Это позволяет RTK Query
работать в «реальной» модели выполнения, при этом получая контролируемые
ответы.
RTK Query формирует запросы через fetchBaseQuery или
кастомный baseQuery. Далее запрос уходит в
fetch. MSW внедряется между приложением и сетью,
перехватывая HTTP-вызовы:
Ключевой момент: RTK Query не знает, что ответ поддельный. Это делает тесты максимально близкими к реальности.
Установка:
npm install msw --save-dev
Для Node.js тестов используется setupServer, для
браузера — setupWorker.
Структура обычно выглядит так:
src/
api/
mocks/
handlers.js
server.js
browser.js
Handlers описывают, какие запросы перехватываются и какие ответы возвращаются.
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/posts', () => {
return HttpResponse.json([
{ id: 1, title: 'Первый пост' },
{ id: 2, title: 'Второй пост' }
]);
}),
http.get('/api/posts/:id', ({ params }) => {
return HttpResponse.json({
id: Number(params.id),
title: 'Пост по id'
});
})
];
MSW позволяет использовать динамические параметры, что особенно важно для RTK Query endpoints с аргументами.
Файл server.js:
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
В конфигурации тестов:
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
resetHandlers особенно важен при тестировании RTK Query,
так как кэш и повторные запросы могут зависеть от состояния моков.
Пример API слоя:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const postsApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({
baseUrl: '/api'
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts'
}),
getPostById: builder.query({
query: (id) => `/posts/${id}`
})
})
});
MSW полностью перехватывает /api/posts и
/api/posts/:id, обеспечивая изолированное тестирование без
реального сервера.
Пример теста с Jest/Vitest:
import { store } from '../store';
import { postsApi } from '../api/postsApi';
import { server } from '../mocks/server';
test('загрузка списка постов', async () => {
const result = store.dispatch(
postsApi.endpoints.getPosts.initiate()
);
const data = await result.unwrap();
expect(data).toHaveLength(2);
expect(data[0].title).toBe('Первый пост');
});
RTK Query возвращает unwrap(), что упрощает работу с
результатом в тестах.
MSW позволяет моделировать HTTP ошибки без изменения логики RTK Query.
import { http, HttpResponse } from 'msw';
server.use(
http.get('/api/posts', () => {
return new HttpResponse(null, { status: 500 });
})
);
Тестирование ошибки:
test('ошибка загрузки постов', async () => {
const result = store.dispatch(
postsApi.endpoints.getPosts.initiate()
);
await expect(result.unwrap()).rejects.toThrow();
});
RTK Query автоматически устанавливает error состояние,
включая status и data.
Реальные API редко отвечают мгновенно, поэтому задержки важны для
проверки состояния isLoading.
import { delay, http, HttpResponse } from 'msw';
http.get('/api/posts', async () => {
await delay(300);
return HttpResponse.json([
{ id: 1, title: 'Пост с задержкой' }
]);
});
RTK Query в этот момент корректно переключает состояния:
isLoadingisFetchingisSuccessMSW позволяет локально переопределять поведение API:
server.use(
http.get('/api/posts', () => {
return HttpResponse.json([]);
})
);
Это полезно для проверки пустых состояний UI и fallback-логики RTK Query.
Одно из ключевых преимуществ RTK Query — кэширование запросов. MSW помогает проверить, что повторные запросы не вызывают лишних сетевых обращений.
test('кэширование запроса posts', async () => {
const first = store.dispatch(
postsApi.endpoints.getPosts.initiate()
);
await first.unwrap();
const second = store.dispatch(
postsApi.endpoints.getPosts.initiate()
);
const data = await second.unwrap();
expect(data).toHaveLength(2);
});
MSW можно дополнительно логировать, чтобы убедиться, что запрос был выполнен один раз.
MSW одинаково хорошо работает с POST, PUT, DELETE.
http.post('/api/posts', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({
id: 123,
...body
});
});
RTK Query mutation:
addPost: builder.mutation({
query: (post) => ({
url: '/posts',
method: 'POST',
body: post
})
})
Тест:
test('создание поста', async () => {
const result = store.dispatch(
postsApi.endpoints.addPost.initiate({
title: 'Новый пост'
})
);
const data = await result.unwrap();
expect(data.id).toBeDefined();
expect(data.title).toBe('Новый пост');
});
RTK Query часто использует prepareHeaders. MSW позволяет
проверять их наличие.
http.get('/api/posts', ({ request }) => {
const auth = request.headers.get('authorization');
if (!auth) {
return new HttpResponse(null, { status: 401 });
}
return HttpResponse.json([]);
});
Это позволяет тестировать сценарии авторизации без реального backend.
RTK Query кэширует данные между вызовами, поэтому важно комбинировать MSW с очисткой store:
afterEach(() => {
server.resetHandlers();
store.dispatch(postsApi.util.resetApiState());
});
Это предотвращает утечки состояния между тестами.
В браузерной среде:
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
Запуск:
worker.start();
Это позволяет тестировать RTK Query прямо в dev-среде без backend.
MSW позволяет моделировать:
http.get('/api/posts', ({ request }) => {
const url = new URL(request.url);
const page = url.searchParams.get('page');
return HttpResponse.json({
page,
items: []
});
});
http.get('/api/posts', () => {
if (Math.random() > 0.5) {
return new HttpResponse(null, { status: 500 });
}
return HttpResponse.json([]);
});
MSW позволяет централизованно управлять всеми API слоями, что важно
при тестировании RTK Query с несколькими api slices.
RTK Query middleware реагирует на:
fulfilledpendingrejectedMSW позволяет искусственно инициировать каждый из этих сценариев через контролируемые ответы. Это делает возможным тестирование side effects, optimistic updates и invalidation логики.
addPost: builder.mutation({
query: (post) => ({ url: '/posts', method: 'POST', body: post }),
invalidatesTags: ['Posts']
});
MSW возвращает успешный POST, после чего RTK Query автоматически
делает refetch getPosts. Это поведение можно наблюдать и
проверять через мок-слой без реального сервера.
При росте проекта handlers обычно разделяются:
mocks/
handlers/
posts.js
auth.js
comments.js
И объединяются:
export const handlers = [
...postsHandlers,
...authHandlers,
...commentsHandlers
];
Это сохраняет управляемость моков при большом числе RTK Query endpoints.