Testando com Jest: 15 dicas para mudar seu dia!

Aquele “a-ha!” que você procurava para seus testes!

Image for post
Image for post
A precisão certa para evitar complexidade nos seus testes!

Diferente de magos, desenvolvedores JS revelam seus segredos! Ano passado, minha ferramenta favorita para testar aplicações React era Mocha + Enzyme, mas, desde o Jest v14, onde foi introduzido o snapshot testing, fui ver de curioso o que era, e acabei achando aquilo incrível! Depois de usar, diariamente, por 8 meses, decidi criar esse artigo para compartilhar o que eu aprendi!

Snapshot e mock de dados, problemas e como resolvê-los

Qual código testar e qual dados você deve fazer mock, não é mais a definição de “testes unitários”, ou de integração, hoje em dia. Hoje, tudo gira em torno de o que o teste valida sobre o cenário de uso.

Mesmo assim, é bem comum você precisar fazer o mock de um componente em um teste, é bem fácil até.

Antes de você me julgar, já digo: Eu normalmente não uso .jsx, mas o syntax highlight do Github não funciona bem com JSX usando .js 😭

Se você está familiarizado com react-router@v4, você já sabe porque esse mock é irado! (dica: você não precisa mais envolver seu teste em um <MemoryRouter/>, ahoy!)

E como funciona named imports?

Bem fácil! Jest vai serializar um componente quando você passar uma string como mock, não importa como. Aliás, se você estiver interessado em saber, me manda um tweet, já adianto que é coisa bem nerd, como JSX é transpilado em JS e outras magias negras).

Precisão cirúrgica ao mockar seus módulos

E o que acontece quando você importa múltiplos módulos, mas você só quer fazer o mock de um deles? Fácil!

No exemplo abaixo, você pode ver que mantemos os avisos de prop-types, mesmo mockando nosso módulo. Você faz um grande favor a você mesmo e ao seu futuro eu, principalmente ao fazer manutenção de dependências e atualziar tudo com confiança. Jest vai estar lá do seu lado e irá te dizer exatamente qual componente foi afetado pela atualização 😉

Porque createElement foi usado aqui? Novamente, se você estiver interessado sobre o lado nerd dessa implementação, me mande um tweet!

Quando você precisa de mais lógica no seu mock, você não pode, simplesmente, retornar uma string :

const Foo = () => 'Foo'

Você irá receber um erro do React dizendo que ele não suporta componentes retornando strings. Isso irá funcionar no Fiber@16, mas hoje em dia, nope! Afinal, nós queremos que o snapshot gerado pelo Jest imprima e não Foo. O exemplo do gist funciona dessa maneira, retornando um componente ao invés de uma string, createElement é seu amigo!

Nota: Sempre use createElement quando strings não são o suficiente.

Exemplos em código são melhores que mil imagens! Vamos ver mais um:

O segredo aqui é usar require.requireActual dentro da função construtora do mock, permitindo que apenas algumas partes do módulo original sejam substituídas. Como não precisamos de validação de prop-types, podemos substituir LoadingBar por uma string, e createElement não é mais necessário. Como não modificamos nada, diferente do example anterior, pode passar o objeto original ReactReduxLoadingBar.

Porém, você viu o elefante na sala? Estamos usando toMatchSnapshot na chamada do mock do dispatch, ao invés de toHaveBeenCalledWith(showLoading)

Image for post
Image for post

Se você nunca tentou usar snapshots para outras coisas ao invés de renderizar componentes React, eu tenho uma novidade para você. Assim como componentes React, existe partes do código que você realmente não precisa saber se retornou um erro ou se foi modificado acidentalmente. A beleza dos testes em snapshot é que você deixa seu código mais amigável para refactors. Mudou um action creators? Mudou a estrutura de dados de um reducer? Mudou os argumentos de um callback? Pare de atualizar tudo isso em dois locais diferentes.

Mas lembre-se:

Image for post
Image for post
Obrigado Tio Ben! Nós iremos nos comportar!

Mock de componentes com funções como children

O famoso function as children. Se você está familiarizado com esse conceito, recomendo gastar alguns minutos lendo esse outro artigo. Porém, não se incomode com buzzwords, ainda são simples de testar 😏.

Ele também funciona para outros casos similares, como o popular padrão render-func.

Estou quase mockando o módulo original inteiro, como achar o balanço correto?

Com Jest, é possível alcançar 100% de cobertura de código. Isso requer uma boa organizanção e balanço entre quais funcionalidades valem a pena ser reimplementadas no seu mock. No exemplo anterior, o funcionamento de render callback do foi implementado, para que o callback executado fosse passado ao snapshot. O exemplo seguinte vai um passo além:

Novamente, nesse exemplo, vale a pena testar a função filha no SidebarLink, mas não é necessário renderizar Somewhere e SomewhereElse, nesse caso, o será serializado no snapshot, da mesma maneira que .

Ah! Se você se encontrar escrevendo o mesmo mock de novo e de novo. Você pode colocar ele em mocks/react-router-dom.js, para acabar com essa farra!

Essa é a chamada plataforma de testes com zero de configuração em ação 😎.

Pare de buscar os avisos de propTypes, deixe o Jest cuidar disso!

Sendo sincero, a melhor solução seria usar FlowType para essas coisas, já que ele faz mais do que o React pode fazer com checagem de proptype. FlowType não requer que você especifique se uma prop é obrigatória ou não, porque ele irá analisar seu código para isso. E, acima disso, ele irá checar os tipos do seu estado, na documentação tem um exemplo irado disso.

Porém, se você não quer pegar esse barco, tudo bem, o plano B é fazer uma pequena modificação na configuração do Jest (tenho certeza que esse PR será aprovado com mais facilidade, já que tipos em JS é algo novo!):

E apenas com essa pequena mudança, você saberá sobre todos os seus testes que tenham avisos de proptype! Como um FlowTypeLite™, afinal, 2% de algo é melhor que 100% de nada, certo?! 😄

Promises — “unhandled promise” nunca mais!

Desde o Jest v20, várias novidades relacionadas a Promises foram introduzidas, e esse problema ficou mais relevante. Em Node v7 (e provavelmente v8, que será o próximo LTS em Outubro, substituindo o atual v6), rejeições não cuidadas irão retornar um erro que irá falhar seu teste em Jest. Isso é muito bom, já que rejeições sem controle são tão ruins quanto um erro retornado de algo. Essas rejeição nunca deveriam passar em branco. O próximo exemplo, mostra como atualizar sua configuração do Jest para ter o mesmo comportamento do Node v8 em versões atuais:

Pera aí Eduardo…Eu pensei que setupFiles fosse executado antes de cada teste em um ambiente isolado, como memory leak pode acontecer?

Boa pergunta, caro companheiro, você está correto do fato de testes serem executados em ambiente isolado. Mas o process é uma variável especial disponível pelo Node, que é compartilhada entre testes, mesmo se os testes estiverem rodando em múltiplos workers. Esse é o motivo da necessidade de adicionar esse guardião de memória! 😝

Se pensarmos em outros aspectos do Node, começar a fazer sentido. Afinal, como seria possível declarar $ NODE_ENV=test jest no seu terminal e todos os seus testes acessarem process.env.NODE_ENV e obterem o valor test como esperado?

Image for post
Image for post
Apesar de não fazer sentido, faz sentido!

setState e eventos DOM, getInstance ao resgate!

O objeto retornado por renderer.create(), em adição ao familiar método .toJS, tem alguns outros: getInstance e update. Falando de getInstance:

E, sim, você pode acessar o props.children e ir rua abaixo na árvore de elementos, mas, antes de fazer isso, lembre-se se vale a pena! Para ser sinceiro, ainda não vi nenhum exemplo em que, atravessar os nós de props.children, não tivesse uma melhor alternativa!

Mockando High Ordem Components (HOC)

Para determinar a melhor estratégia de mock, você precisar identificar o que o seu componente está usando da API que você criou para seu HOC. Nesse exemplo, nós vamos ver a função connect do react-redux, que é uma HOC bem complexa, com várias funcionalidades próprias, porém, nosso componente está usando apenas a API dispatch da nossa redux store, nosso mock não precisa ser complicado:

Mas como a vida real é diferente, existe casos que não é tão simples assim. Por exemplo, usando redux-form, você precisa passar uma propriedade validate para ter acesso aos valores do formulário, assim como as props passadas ao seu componente. Nós vamos usar uma abordagem parecida, porém, vamos passar um valor de string que possa ser traduzido para outros idiomas e vamos testar o mock para ter certeza que validate está sendo chamado com as props corretas, garantido que nossa validação não falhe em um futuro próximo devido a um refactor:

Testando métodos do ciclo de vida do componente quando getInstance não consegue resolver o problema

Usando getInstance, irá permitir que você inspecione a maior parte dos ciclos de vida de um componente React que estão disponíveis quando usamos setState. Mas, e como testamos aqueles componentes que usam props ao invés de state?

Vamos dar uma ver como podemos testar shouldComponenteUpdate de maneira correta, usando o método update da API do react-test-renderer:

Testando lógica quando desmontamos o componente

E aquele velho conhecido: ”setState cannot be called on unmounted components” no console do navegador? Como podemos testar que nossa lógica ao desmontar o componente? Aqui está o como:

Faça mock global de componentes que usem .context, testando react-intl como Merlin!

E para o grande final, vamos combinar as dicas anteriores com mais alguns truques para fazer mock de componentes bem complicados! O que é mais complicado que internacionalizar sua UI em múltiplos idiomas e mostrar conteúdo baseado na localização do usuário? Testar tudo isso! :P

Nesse exemplo, vou usar react-intl como nosso componente para i18n. Na documentação oficial, é recomendado criar um wrapper para renderer.create chamado createComponentWithIntl. O lado positivo disso é que ele faz com as HOCs do react-intl, Formatted, mostrem o mesmo conteúdo que em produção, deixando seus snapshots mais próximos da realidade do usuário. O problema disso é que ele envolve cada teste em um IntlProvider quando você chama componente.getInstance() você terá uma instância do IntlProvider ao invés do seu componente. O exemplo abaixo mostra como é possível usar um mock global, que é uma solução mais elegante, disponibilizando os benefícios do createComponentWithIntl, enquanto evitamos a mudança de comportamento do getInstance:

E depois desse exemplo espetacular, é hora de finalizar!👌

Se você chegou até aqui, obrigado por ler! Eu realmente espero que você tenha aprendido algo novo que possa te ajudar no seu dia-a-dia.

Se você tiver qualquer feedback e quiser deixar um comentário, por favor, faça! E também pode me mandar um tweet!

Ah! Todos os exemplos estão formatados com prettier! ❤️

Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store