Тестирование Vue Query и авторизации (мок Keycloak)
↑ 5.7 Тестирование приложения · ← Предыдущая
Зачем это на собесе
Тесты кэша запросов и защищённых экранов без реального провайдера идентичности.
Объяснение
Vue Query
import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query";
function makeQueryClient() {
return new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: Infinity, staleTime: 0 }, mutations: { retry: false } } }); // без ретраев и общего кэша
}
function renderWithQuery(component: Component, opts = {}) {
const queryClient = makeQueryClient();
return { queryClient, ...render(component, { global: { plugins: [[VueQueryPlugin, { queryClient }], pinia, router] }, ...opts }) };
}
it("показывает заказы", async () => {
renderWithQuery(OrdersPage);
expect(screen.getByText(/загрузка/i)).toBeInTheDocument();
expect(await screen.findByText("A-1")).toBeInTheDocument(); // данные пришли из MSW
});
it("после создания заказа список обновляется (инвалидация)", async () => {
const { queryClient } = renderWithQuery(OrdersPage);
const spy = vi.spyOn(queryClient, "invalidateQueries");
await user.click(screen.getByRole("button", { name: /создать/i }));
await waitFor(() => expect(spy).toHaveBeenCalledWith({ queryKey: ["orders"] }));
});
// Composable с Query
const [{ data, isSuccess }] = withSetup(() => useOrders(ref({ page: 1 })), { plugins: VueQueryPlugin, { queryClient } });
await vi.waitFor(() => expect(isSuccess.value).toBe(true));Оптимистичное обновление: MSW возвращает ошибку → проверяем откат состояния списка.
Авторизация (мок Keycloak)
// test/mocks/keycloak.ts
export const keycloakMock = { authenticated: true, token: "test-token", tokenParsed: { sub: "1", preferred_username: "anna", realm_access: { roles: ["user"] } },
login: vi.fn(), logout: vi.fn(), updateToken: vi.fn().mockResolvedValue(true), init: vi.fn().mockResolvedValue(true), onTokenExpired: undefined };
vi.mock("@/auth/keycloak", () => ({ keycloak: keycloakMock, initAuth: vi.fn() }));
it("добавляет Bearer в запросы", async () => {
let auth = "";
server.use(http.get("/api/me", ({ request }) => { auth = request.headers.get("authorization") ?? ""; return HttpResponse.json({}); }));
await api.get("/me");
expect(auth).toBe("Bearer test-token");
});
it("без роли admin — кнопка скрыта", () => {
keycloakMock.tokenParsed.realm_access.roles = ["user"];
render(UsersPage, ...); expect(screen.queryByRole("button", { name: /удалить/i })).toBeNull();
});
it("401 → обновление токена → повтор", async () => { /* MSW: первый запрос 401, второй 200; проверяем updateToken и результат */ });Практика: выделяйте слой аутентификации за интерфейсом (useAuth) — в тестах подменяется стор/composable, не библиотека.
Нюансы и подводные камни
- Новый
QueryClientна каждый тест, иначе кэш протекает. retry: falseв тестах, иначе ошибки «зависают» на повторах.- Не забывайте очищать моки Keycloak между тестами (
vi.clearAllMocks()). - Токены в реальном формате JWT не нужны; важна форма
tokenParsed. - Асинхронные проверки —
findBy/waitFor, а неsetTimeout.
Практика
- Напишите
renderWithQueryи протестируйте состояния загрузки, успеха и ошибки. - Смоделируйте мок Keycloak и проверьте матрицу ролей.
- Протестируйте обновление токена при 401 с MSW.
Вопросы с ответами
Почему в тестах Vue Query отключают retry?
Чтобы ошибки не откладывались повторами и тест не «висел» до исчерпания попыток.
Как тестировать авторизованные экраны без Keycloak?
Замокать модуль аутентификации (или
useAuth) с нужными claims/ролями и проверять поведение UI и заголовки запросов.