RTK Query предоставляет встроенные механизмы для подмены сетевого слоя и тестирования API без реальных HTTP-запросов. Мокирование запросов используется для изоляции бизнес-логики от инфраструктуры, ускорения тестов и создания предсказуемого поведения API в разработке и CI.
RTK Query строится поверх Redux Toolkit и использует единый слой
API-сервиса, который инкапсулирует логику запросов. Основная точка
расширения для мокирования — baseQuery.
baseQuery представляет собой функцию, которая принимает
описание запроса и возвращает результат. Именно через её замену
реализуется подмена реальных HTTP-вызовов.
Типичная сигнатура:
const baseQuery = async (args, api, extraOptions) => {
return { data: result }
}
Мокирование сводится к созданию альтернативного
baseQuery, который возвращает заранее определённые данные
вместо обращения к серверу.
Наиболее прямолинейный способ — заменить fetchBaseQuery
собственной реализацией.
import { createApi } fr om '@reduxjs/toolkit/query/react'
const mockBaseQuery = async (args) => {
if (args.url === '/users') {
return {
data: [
{ id: 1, name: 'Alex' },
{ id: 2, name: 'Maria' }
]
}
}
return { error: { status: 404, data: 'Not found' } }
}
export const api = createApi({
reducerPath: 'api',
baseQuery: mockBaseQuery,
endpoints: (builder) => ({
getUsers: builder.query({
query: () => ({ url: '/users', method: 'GET' })
})
})
})
В этом варианте логика мокирования полностью сосредоточена в одном месте. Поведение API становится детерминированным.
Часто требуется различать поведение в зависимости от окружения: development, test, storybook.
const realBaseQuery = fetchBaseQuery({
baseUrl: 'https://api.example.com'
})
const mockBaseQuery = async (args) => {
switch (args.url) {
case '/users':
return { data: [{ id: 1, name: 'Test User' }] }
default:
return { error: { status: 500, data: 'Unknown endpoint' } }
}
}
const baseQuery =
process.env.NODE_ENV === 'test'
? mockBaseQuery
: realBaseQuery
Такой подход позволяет сохранять один API слой при разных источниках данных.
Для приближения к реальному поведению API добавляется искусственная задержка.
const delay = (ms) => new Promise(res => setTimeout(res, ms))
const mockBaseQuery = async (args) => {
await delay(300)
if (args.url === '/users') {
return {
data: [
{ id: 1, name: 'Delayed User' }
]
}
}
return { error: { status: 404, data: 'Not found' } }
}
Искусственная задержка позволяет тестировать состояния загрузки
(isLoading, isFetching) и UI-реакции на
асинхронность.
RTK Query позволяет модифицировать ответ через
transformResponse. Это даёт возможность мокировать данные
на уровне конкретного endpoint, не затрагивая
baseQuery.
getUsers: builder.query({
query: () => '/users',
transformResponse: (response) => {
return [
...response,
{ id: 999, name: 'Injected User' }
]
}
})
Этот подход полезен, когда требуется частичное изменение данных без полной подмены API слоя.
RTK Query поддерживает динамическое расширение API через
injectEndpoints. Это позволяет создавать отдельные мок-слои
поверх базового API.
const mockApi = api.injectEndpoints({
endpoints: (builder) => ({
getUsersMock: builder.query({
queryFn: async () => {
return {
data: [
{ id: 1, name: 'Injected Mock' }
]
}
}
})
})
})
queryFn полностью заменяет baseQuery для
конкретного endpoint, что делает его идеальным инструментом для
точечного мокирования.
queryFn является альтернативой query и даёт
полный контроль над логикой запроса.
getUserById: builder.query({
queryFn: async (id) => {
if (id === 1) {
return { data: { id: 1, name: 'Local User' } }
}
return { error: { status: 404, data: 'User not found' } }
}
})
Этот способ полностью исключает baseQuery, включая любые
middleware-эффекты.
Для масштабных приложений применяется фабрика baseQuery, позволяющая подменять зависимости.
const createBaseQuery = ({ mock }) => {
if (mock) {
return async (args) => {
return { data: { mock: true, args } }
}
}
return fetchBaseQuery({ baseUrl: '/api' })
}
const api = createApi({
baseQuery: createBaseQuery({ mock: true }),
endpoints: (builder) => ({
getData: builder.query({
query: () => '/data'
})
})
})
Такой подход обеспечивает централизованное управление режимами работы API.
Тестирование API невозможно без симуляции ошибок: таймаутов, 500-х статусов, некорректных данных.
const mockBaseQuery = async (args) => {
if (args.url === '/timeout') {
return {
error: {
status: 'TIMEOUT',
data: 'Request exceeded time lim it'
}
}
}
if (args.url === '/server-error') {
return {
error: {
status: 500,
data: 'Internal Server Error'
}
}
}
return { data: {} }
}
Такая модель позволяет проверять устойчивость UI и корректность
обработки error состояния в RTK Query.
RTK Query кэширует данные по ключам endpoint + args. При
мокировании важно сохранять стабильность возвращаемых значений, иначе
кэш может вести себя непредсказуемо.
const mockUsers = [
{ id: 1, name: 'Static User' }
]
const mockBaseQuery = async () => {
return {
data: mockUsers
}
}
Стабильные ссылки на данные позволяют корректно тестировать селекторы и мемоизацию.
Хотя RTK Query поддерживает встроенное мокирование, часто применяется Mock Service Worker как перехватчик сетевых запросов.
import { rest } from 'msw'
export const handlers = [
rest.get('/users', (req, res, ctx) => {
return res(
ctx.json([
{ id: 1, name: 'MSW User' }
])
)
})
]
RTK Query при этом остаётся неизменённым, а подмена происходит на уровне сети, что ближе к реальному поведению браузера.
При тестировании RTK Query используется конфигурация store с подменённым API slice.
import { configureStore } from '@reduxjs/toolkit'
import { api } from './api'
const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware)
})
Если baseQuery мокирован, весь store автоматически
работает в тестовом режиме без изменений компонентов.
На практике часто используется комбинация:
baseQuery для глобального мокированияqueryFn для точечных кейсовtransformResponse для локальных измененийТакое разделение позволяет моделировать разные уровни реальности API без дублирования логики.
Неправильная структура ответа baseQuery приводит к
некорректной работе RTK Query. Всегда требуется строгое соответствие
одному из вариантов:
{ data: ... }{ error: ... }Возврат произвольных объектов ломает нормализацию состояния.
Другой частой ошибкой является изменение структуры данных между вызовами, что приводит к лишним перерендерингам и сбросу кэша.
RTK Query middleware продолжает работать независимо от источника данных. Это означает, что:
onQueryStarted вызывается всегдаcache lifecycle сохраняетсяinvalidatesTags работает как обычноМокирование не отключает внутреннюю логику Redux, а лишь подменяет источник данных.