O código de teste de unidade que depende de APIs externas apresenta uma variedade de desafios. Algumas APIs externas são lentas, sujeitas a erros devido a interrupções na rede, sujeitas a limites de taxa e difíceis de rastrear porque podem mudar ou desaparecer sem aviso prévio. Felizmente, esses problemas são relativamente fáceis de superar.

Neste artigo, examinaremos dois dos padrões mais comumente usados ​​para testar o código que consome uma API de terceiros. Vou demonstrar como ir de um teste que chama a API externa diretamente para um que remove a dependência externa por:

  • Zombar de solicitações com respostas predefinidas
  • Interceptando as solicitações e persistindo em suas respostas para uso posterior

Teste de código que consome uma API externa

Para demonstrar o código de teste que consome uma API externa, usaremos um exemplo que recupera uma série de postagens filtradas pelo ID do usuário fornecido por espaço reservado JSON .

//post.js
const fetch=require ('node-fetch'); getPosts função assíncrona (userId) { resposta const=aguarda busca ( `https://jsonplaceholder.typicode.com/posts?userId=$ {userId}` ); if (! response.ok) { lance novo erro (response.statusText); } retornar, aguardar resposta.json ();
} module.exports={ getPosts,
};

Vamos supor que você tenha uma função que chama uma API externa como a mostrada acima e produz uma resposta JSON no seguinte formato de :

 [ { "userId": 1, "id": 1, "title":"sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "corpo":"quia et suscipit \ nsuscipit recusandae consequuntur expedita et cum \ nreprehenderit molestiae ut ut quas totam \ nnostrum rerum est autem sunt rem eveniet architecto" }, { "userId": 1, "id": 2, "título":"qui est esse", "corpo":"est rerum tempore vitae \ nsequi sint nihil reprehenderit dolor beatae e a dolores neque \ nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis \ nqui aperiam non debitis possimus qui neque nisi nulla" }
]

Agora, vamos escrever um teste que confirma que as postagens retornadas têm um userId que corresponde ao valor que foi passado para a função getPost () . Embora eu tenha optado por usar Jest para este teste devido à sua popularidade, os mesmos princípios podem ser aplicados a outras estruturas de teste como Mocha ou Jasmine .

//post.test.js
const {getPosts}=require ('./post'); test ('retornar uma lista de postagens de um usuário', ()=> { const userId=1; retornar getPosts (userId).then ((dados)=> { expect (data.length).toBeGreaterThan (0); data.forEach ((post)=> { esperar (postar).toEqual ( expect.objectContaining ({ ID do usuário, }) ); }); });
});

O teste acima confirma que os dados esperados não estão vazios e que o userId em cada objeto corresponde ao argumento da função. O problema com esse teste é que ele chama a API real, o que o torna lento para terminar porque uma solicitação real está sendo enviada pela rede. Por exemplo, este teste específico levou mais de um segundo para ser executado na minha máquina.

Função GetPost de UserId de teste externo da API

Embora um teste lento provavelmente não seja um grande problema por si só, todo o conjunto de testes se torna progressivamente mais lento e mais frágil quando você executa muitos desses testes, que podem ser afetados por problemas de conectividade e limites de taxa.

Além disso, algumas APIs requerem autenticação ou autorização. Se for fornecido por um serviço pago, pode ficar caro chamar a API. Por esses motivos, é importante desacoplar os testes das chamadas de API usando as estratégias descritas abaixo.

Simulação manual da solicitação HTTP

Mocking é uma abordagem para teste de unidade em que dependências externas são substituídas por objetos que simulam seu comportamento. Isso torna mais fácil isolar o código que está sendo testado de suas dependências externas e evitar os efeitos colaterais negativos que resultam de interações com um sistema externo. No caso de APIs externas, a simulação envolve a interceptação da solicitação HTTP e o retorno da resposta esperada no cenário real.

A biblioteca Jest fornece métodos úteis para simular uma dependência externa. Nesse caso, queremos simular chamadas para fetch na função getPosts para que ela retorne uma resposta pronta em vez de chegar à API real. Isso pode ser feito no arquivo de teste:

//post.test.js
jest.mock ('node-fetch'); const fetch=require ('node-fetch');
const getPostResponse=require ('./testdata/postResponse');
const {getPosts}=require ('./post');
const {Response}=jest.requireActual ('node-fetch'); test ('retornar uma lista de postagens de um usuário', ()=> { const userId=1; fetch.mockResolvedValue (nova resposta (JSON.stringify (getPostResponse))); retornar getPosts (userId).then ((dados)=> { expect (data.length).toBeGreaterThan (0); data.forEach ((post)=> { esperar (postar).toEqual ( expect.objectContaining ({ ID do usuário, }) ); }); });
});

Na primeira linha, o método jest.mock () é usado para simular todo o módulo node-fetch . Isso define cada exportação do módulo para jest.fn () , que retorna indefinido . Com isso implementado, você pode atribuir o valor de retorno de qualquer método exportado por node-fetch a qualquer valor que desejar.

No fragmento acima, fetch é atribuído a um valor de retorno de uma promessa que resolve para uma resposta predefinida por meio do método mockResolvedValue () . O auxiliar jest.requireActual () é necessário aqui para reter o comportamento real do objeto Response em vez de uma versão simulada, que não está em conformidade com o original.

A resposta esperada deve ser colocada em um arquivo separado antes que o teste seja executado:

//testdata/postResponse.js
module.exports=[ { userId: 1, id: 1, título: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', corpo: 'quia et suscipit \ nsuscipit recusandae consequuntur expedita et cum \ nreprehenderit molestiae ut ut quas totam \ nnostrum rerum est autem sunt rem eveniet architecto', }, { userId: 1, id: 2, título:'qui est esse', corpo: 'est rerum tempore vitae \ nsequi sint nihil reprehenderit dolor beatae e a dolores neque \ nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis \ nqui aperiam non debitis possimus qui neque nisi nulla', }, //truncado por brevidade
];

Se você executar o teste novamente, ele não chamará mais a API externa. Em vez disso, ele recuperará a resposta do arquivo especificado e a usará como o valor resolvido de fetch para que o teste ainda passe:

Teste, recupere a resposta, busque o valor resolvido

Se você quiser ter certeza de que está testando em relação à resposta predeterminada, edite um dos campos no objeto de resposta para que o teste falhe. Por exemplo, você pode alterar um dos campos userId para 2 . Você receberá um erro como o mostrado abaixo:

Mensagem de erro de falha no teste

Embora a simulação possa ser uma forma eficaz de testar a unidade de código que depende de serviços externos, ela tem algumas falhas.

A resposta simulada pode ficar fora de sincronia com a API real, o que pode levar a resultados potencialmente enganosos. Seus testes serão bem-sucedidos, mas sua função pode falhar na produção se a dependência externa mudar e a simulação permanecer baseada na versão antiga. Isso é especialmente provável se houver alterações significativas que não foram consideradas no objeto simulado.

Além disso, a quantidade de manutenção que você precisa realizar nos próprios testes pode aumentar se você usar excessivamente objetos fictícios.

Portanto, ao usar essa técnica, é importante se manter atualizado com quaisquer alterações na forma dos dados que a API retorna. Felizmente, isso pode ser automatizado com ferramentas como Nock , que discutiremos na próxima seção.

Gravar, salvar e reutilizar respostas da API com Nock

O Nock pode ser usado para simular respostas individuais da API de uma forma semelhante a como usamos jest.mock () na seção anterior. Como qualquer outro pacote Node, você deve instalá-lo primeiro, antes de exigi-lo em seu script:

 $ yarn adicionar nock-D

No snippet abaixo, o Nock é usado para interceptar uma solicitação GET especificando o nome do host e o caminho da solicitação junto com o código de status HTTP esperado e o corpo da resposta. Isso permite que Nock intercepte a solicitação de getPosts () para que o corpo de resposta especificado seja retornado e testado.

 const nock=require ('nock');
const {getPosts}=require ('./post');
const getPostResponse=require ('./testdata/postResponse'); test ('retornar uma lista de postagens de um usuário', ()=> { const userId=1; nock ('https://jsonplaceholder.typicode.com') .get (`/posts? userId=$ {userId}`) .reply (200, getPostResponse); retornar getPosts (userId).then ((dados)=> { expect (data.length).toBeGreaterThan (0); data.forEach ((post)=> { esperar (postar).toEqual ( expect.objectContaining ({ ID do usuário, }) ); }); });
});

Nock tem outro truque na manga que nos permite salvar a resposta real da interceptação de uma solicitação HTTP para uso posterior; ele registra a saída de uma solicitação de API externa e a adiciona como um acessório na execução inicial. Na próxima vez que o teste for executado, o acessório gravado será usado. Veja como usá-lo:

 const nockBack=require ('nock'). back;
const path=require ('path');
const {getPosts}=require ('./post'); nockBack.fixtures=path.join (__ dirname,'__nock-fixtures__');
nockBack.setMode ('registro'); test ('retornar uma lista de postagens de um usuário', async ()=> { const userId=1; const {nockDone}=espera nockBack ('post-data.json'); dados const=espera getPosts (userId); expect (data.length).toBeGreaterThan (0); data.forEach ((post)=> { esperar (postar).toEqual ( expect.objectContaining ({ ID do usuário, }) ); }); nockDone ();
});

Antes de executar o teste, você precisa configurar a localização das respostas registradas por meio da propriedade fixtures . Depois, defina o modo para registro para que quaisquer Nocks registrados sejam usados ​​e os ausentes sejam registrados.

Dentro da função de teste, passe o nome de um arquivo para nockBack para que a saída gravada seja salva lá. Assim que as expectativas do teste forem confirmadas, chame o método nockDone () .

Quando o teste é executado pela primeira vez, Nock enviará a solicitação real para a API real e gravará a resposta para um arquivo em __nock-fixtures __/post-data.json . Em execuções subsequentes, a resposta registrada é usada em vez de atingir a API gravada novamente.

Executar teste Nock Recorde d Resposta

O principal benefício dessa abordagem é que você não precisa mais simular manualmente cada solicitação HTTP. Isso permite que você evite resultados potencialmente enganosos e manutenção desnecessária. Também é fácil regravar um aparelho simplesmente removendo uma versão salva anteriormente antes de executar o teste.

Conclusão

Zombar é uma maneira prática de manter seus testes rápidos e determinísticos. Com a simulação, você pode remover qualquer acoplamento com dependências externas para que não sejam mais uma restrição para a unidade em teste. Sem simulação, pode ser difícil diagnosticar se um teste falhou devido ao nosso código ou dependências.

Neste artigo, consideramos duas abordagens para simular APIs externas em um conjunto de testes Node.js, mas apenas arranhamos a superfície do que é possível usando Jest e Nock a esse respeito. Ambos têm documentação muito detalhada, que vale a pena explorar.

Obrigado por ler e feliz teste!

A postagem Como testar o código que depende de APIs externas em Node.js apareceu primeiro no LogRocket Blog .

Source link