Testes unitários de hooks
Tenha cuidado ao usar jest.mock em módulos como o Reactive Data Client. Eliminar exports esperados
pode levar a erros difíceis de rastrear,
como TypeError: Class extends value undefined is not a function or null.
Em vez disso, faça um mock parcial ou, melhor ainda, use mockResolvedValue nos seus endpoints.
Hooks permitem extrair comportamentos complexos dos seus componentes para funções concisas e
componíveis. Isso pode facilitar muito o teste do comportamento dos componentes. Mas como isso funciona se você quiser usar hooks do Reactive Data Client?
Fornecemos alguns utilitários simples para reduzir o código boilerplate dos testes unitários, que são wrappers em torno do renderHook() do @testing-library/react-hooks.
Queremos uma função renderDataHook() que renderize no contexto tanto de
um Provider quanto de um limite de Suspense.
Isso geralmente é feito durante a configuração dos testes. A limpeza é executada automaticamente após cada teste.
renderDataHook() cria um contexto de Provider com novas instâncias de manager. Isso significa que cada chamada
de renderDataHook() resultará em um estado de cache totalmente novo, assim como um estado de manager novo.
Polyfill de fetch no node < 18
O Node não vem com fetch por padrão, então precisamos garantir que ele receba um polyfill.
- NPM
- Yarn
- pnpm
- esm.sh
yarn add --dev whatwg-fetch
npm install --saveDev whatwg-fetch
pnpm add -D whatwg-fetch
<script type="module">
import * from 'https://esm.sh/whatwg-fetch?dev';
</script>
Jest
// jest.config.js
module.exports = {
// other things
setupFiles: ['./testSetup.js'],
};
// testSetup.js
require('whatwg-fetch');
Exemplo:
- @data-client/react
- @data-client/react/redux
import nock from 'nock';
import { useSuspense } from '@data-client/react';
import { renderDataHook } from '@data-client/test';
import { ArticleResource } from '../resources/Article';
describe('useSuspense()', () => {
beforeEach(() => {
nock(/.*/)
.persist()
.defaultReplyHeaders({
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
})
.options(/.*/)
.reply(200)
.get(`/article/0`)
.reply(403, {});
});
afterEach(() => {
nock.cleanAll();
});
it('should throw errors on bad network', async () => {
const { result, waitFor } = renderDataHook(() => {
return useSuspense(ArticleResource.get, {
title: '0',
});
});
expect(result.current).toBeUndefined();
await waitFor(() => expect(result.current).toBeDefined());
expect(result.error).toBeDefined();
expect((result.error as any).status).toBe(403);
});
});
import nock from 'nock';
import { useSuspense } from '@data-client/react';
import { makeRenderDataHook } from '@data-client/test';
import { DataProvider } from '@data-client/react/redux';
import { ArticleResource } from '../resources/Article';
describe('useSuspense()', () => {
let renderDataHook: ReturnType<typeof makeRenderDataHook>;
beforeEach(() => {
nock(/.*/)
.persist()
.defaultReplyHeaders({
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
})
.options(/.*/)
.reply(200)
.get(`/article/0`)
.reply(403, {});
renderDataHook = makeRenderDataHook(DataProvider);
});
afterEach(() => {
nock.cleanAll();
});
it('should throw errors on bad network', async () => {
const { result, waitFor } = renderDataHook(() => {
return useSuspense(ArticleResource.get, {
title: '0',
});
});
expect(result.current).toBeUndefined();
await waitFor(() => expect(result.current).toBeDefined());
expect(result.error).toBeDefined();
expect((result.error as any).status).toBe(403);
});
});