Интеграционное тестирование RTK Query строится вокруг проверки взаимодействия нескольких уровней системы: Redux store, API slice, middleware, кеширования и сетевого слоя. В отличие от юнит-тестов, где изолируется отдельная функция или редьюсер, здесь проверяется поведение системы как единого механизма — от инициализации запроса до попадания данных в кеш и последующего чтения из store.
RTK Query тесно связан с Redux Toolkit и работает через middleware, что делает его особенно удобным для интеграционных сценариев. Основная цель тестирования — подтвердить корректность цепочки:
инициация запроса → middleware обработка → выполнение запроса (или mock) → нормализация ответа → запись в cache → селекторы → реактивное обновление состояния
Интеграционные тесты RTK Query обычно строятся на реальном Redux
store с подключённым API slice. При этом сетевой слой заменяется моками
через fetchBaseQuery или через глобальный
fetch.
Ключевые зависимости:
Для интеграционного тестирования важно использовать максимально приближённую к реальной конфигурацию store.
import { configureStore } from '@reduxjs/toolkit';
import { api } from '../services/api';
export const createTestStore = () =>
configureStore({
reducer: {
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
});
Здесь принципиально важно подключение api.middleware,
так как именно он управляет жизненным циклом запросов, кешированием и
инвалидированием данных.
MSW позволяет перехватывать реальные HTTP-запросы и возвращать контролируемые ответы, не вмешиваясь в код RTK Query.
import { rest } from 'msw';
import { setupServer } from 'msw/node';
export const handlers = [
rest.get('/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([{ id: 1, name: 'Alex' }])
);
}),
];
export const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
RTK Query позволяет вызывать endpoints напрямую через
dispatch.
import { createTestStore } from './store';
import { api } from '../services/api';
test('fetch users loads data into store', async () => {
const store = createTestStore();
const result = await store.dispatch(
api.endpoints.getUsers.initiate()
);
const state = store.getState();
expect(result.data).toEqual([{ id: 1, name: 'Alex' }]);
expect(state.api.queries['getUsers(undefined)'].status).toBe('fulfilled');
});
Одним из ключевых аспектов RTK Query является кеширование результатов. Интеграционное тестирование должно проверять, что повторный запрос не приводит к лишнему сетевому вызову.
test('caches query result and avoids refetch', async () => {
const store = createTestStore();
await store.dispatch(api.endpoints.getUsers.initiate());
await store.dispatch(api.endpoints.getUsers.initiate());
const queries = Object.keys(store.getState().api.queries);
expect(queries.length).toBe(1);
});
Поведение кеша зависит от keepUnusedDataFor и
refetchOnMountOrArgChange, поэтому в тестах важно
фиксировать эти параметры в API slice.
RTK Query может автоматически обновлять данные при:
test('refetch on tag invalidation', async () => {
const store = createTestStore();
await store.dispatch(api.endpoints.getUsers.initiate());
store.dispatch(
api.util.invalidateTags([{ type: 'User', id: 'LIST' }])
);
const state = store.getState();
const query = state.api.queries['getUsers(undefined)'];
expect(query?.endpointName).toBe('getUsers');
});
RTK Query управляет состояниями:
pendingfulfilledrejectedИнтеграционные тесты часто проверяют переходы между ними.
test('loading states transition correctly', async () => {
const store = createTestStore();
const promise = store.dispatch(api.endpoints.getUsers.initiate());
let state = store.getState();
expect(state.api.queries['getUsers(undefined)']?.status).toBe('pending');
await promise;
state = store.getState();
expect(state.api.queries['getUsers(undefined)']?.status).toBe('fulfilled');
});
Ошибочные сценарии критичны для проверки устойчивости интеграции.
test('handles request error', async () => {
server.use(
rest.get('/users', (req, res, ctx) => {
return res(ctx.status(500));
})
);
const store = createTestStore();
const result = await store.dispatch(
api.endpoints.getUsers.initiate()
);
expect(result.error).toBeDefined();
expect(result.error.status).toBe(500);
});
Хотя интеграционные тесты чаще фокусируются на store, RTK Query активно используется через hooks.
import { renderHook, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { createTestStore } from './store';
import { useGetUsersQuery } from '../services/api';
test('hook returns data from API', async () => {
const store = createTestStore();
const wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
const { result } = renderHook(() => useGetUsersQuery(), {
wrapper,
});
await waitFor(() => {
expect(result.current.data).toBeDefined();
});
});
Здесь проверяется полная цепочка: hook → middleware → request → store → rerender.
RTK Query предотвращает дублирование одинаковых запросов.
test('deduplicates identical requests', async () => {
const store = createTestStore();
const promise1 = store.dispatch(api.endpoints.getUsers.initiate());
const promise2 = store.dispatch(api.endpoints.getUsers.initiate());
await Promise.all([promise1, promise2]);
const state = store.getState();
const queryState = state.api.queries['getUsers(undefined)'];
expect(queryState?.requestId).toBeDefined();
});
RTK Query хранит данные пока есть активные подписчики.
test('removes cache after last unsubscribe', async () => {
const store = createTestStore();
const subscription = store.dispatch(
api.endpoints.getUsers.initiate()
);
subscription.unsubscribe();
const state = store.getState();
expect(state.api.queries['getUsers(undefined)']).toBeUndefined();
});
Интеграционные сценарии часто включают цепочки запросов.
test('handles multiple endpoints interaction', async () => {
const store = createTestStore();
await store.dispatch(api.endpoints.getUsers.initiate());
await store.dispatch(api.endpoints.getUserById.initiate(1));
const state = store.getState();
expect(state.api.queries['getUsers(undefined)']).toBeDefined();
expect(state.api.queries['getUserById(1)']).toBeDefined();
});
RTK Query использует теги для синхронизации данных между endpoint’ами.
test('updates related queries via tags', async () => {
const store = createTestStore();
await store.dispatch(api.endpoints.getUsers.initiate());
store.dispatch(
api.util.invalidateTags([{ type: 'User', id: 1 }])
);
const state = store.getState();
expect(state.api.queries['getUsers(undefined)']?.isFetching).toBe(true);
});
Интеграционные тесты RTK Query обычно направлены на проверку следующих свойств системы:
Каждый из этих аспектов проверяется через взаимодействие нескольких слоёв системы, а не через изолированные функции или модули