Fala galera Tech, beleza?
Gostaria de compartilhar com vocês um método de como criar testes para eventos HTML. Vou fazer isso compartilhando um componente de carregamento de imagem que construi.
Para contextualizar, neste componente eu recebo duas propriedades importantes: src
e fallBackImage
:
const ImageLoader = ({ src, fallbackImage = '' }) => {
const [isLoading, setIsLoading] = useState(true);
const imageRef = useRef(null);
const onLoadFinished = () => {
setIsLoading(false);
};
const onLoadError = () => {
const imageObject = imageRef.current;
imageObject.src = fallbackImage;
setIsLoading(false);
imageObject.onerror = null;
};
useEffect(() => {
const imageObject = imageRef.current;
imageObject.src = src;
}, [src]);
return (
<Container isLoading={isLoading}>
<Image
ref={imageRef}
onError={onLoadError}
onLoad={onLoadFinished}
/>
{isLoading && <div>Carregando...</div>}
</Container>
);
};
export default ImageLoader;
É um componente simples onde monitoramos os eventos onload e onerror.
O foco não é o componente, mas é interessante tê-lo em mente para compreendermos os testes.
Através desses eventos conseguimos capturar se houve o sucesso ou falha no carregamento da imagem. No caso de falha, setamos o src com a propriedade fallbackImage através da função onLoadError
. Veja:
const onLoadError = () => {
const imageObject = imageRef.current;
imageObject.src = fallbackImage;
setIsLoading(false);
imageObject.onerror = null;
};
Construindo o teste.
Para testes, usaremos a biblioteca padrão do React, a RTL - React Testing Library. A proposta desta biblioteca é testar o comportamento do usuário e não a implementação do código. Podemos pensar que testamos o que o usuário está vendo e não o que está no código.
Vamos trabalhar com dois casos de teste.
O primeiro caso vai garantir que o elemento esteja na tela. Ele existe devido ao uso do TDD e é um dos primeiros testes a serem feitos:
it('should be able to render ProgressiveImage component', async () => {
const { container } = render(<ImageLoader src="test" />);
expect(container.firstChild).toBeInTheDocument();
});
Conforme a proposta do RTL, não testamos a implementação, mas se o usuário vai ver o elemento na tela.
O segundo caso de teste vai garantir que nosso componente de imagem está recebendo o caminho da imagem corretamente, tanto no caso de sucesso ou falha. Para este teste não temos como carregar a imagem, pois é um teste de unidade, mas podemos simular se nossa lógica dos eventos está funcionando.
Para este tipo de teste eu não fazia ideia como simular os eventos, pensei colocar dentro do código uma refência da imagem e usar o método dispatchEvent do próprio HTML disparando o evento load ou *error, mas não me parecia muito semântico e queria evitar um pouco de problemas no Code Review.
Durante a pesquisa, encontrei com algo muito interessante que junto como próprio React que é uma biblioteca utils de teste. Estou falando do ReactTestUtils.
Numa primeira vista ele resolvia meu problema e resolveu mesmo. Vamos acompanhar o código do teste para a condição de imagem carregada com sucesso:
it('should be able to show skeleton during the loading and when finished loading image needs to have src property equals send by parameter.', async () => {
render(
<ImageLoader
src="image-teste.png"
fallbackImage="fallback-image.png"
alt="image-test"
/>,
);
const imageRef = screen.getByAltText('image-test');
ReactTestUtils.Simulate.load(imageRef);
expect(imageRef).toHaveAttribute('src', 'image-teste.png');
});
Me pareceu um código semântico e funcionou muito bem. Inclusie meu PR foi aberto com essa solução e foi aprovado.
Porém dias depois fazendo algumas revisões nos testes, pois queria escrever sobre essa solução para mais pessoas, descobri que a biblioteca RTL também tem uma função que faz exatamente a mesma coisa (possivelmente construida sob o ReactTestUtils) que é a função fireEvent.
Eu já usei essa função antes para disparar eventos de click, mas migrei para o userEvent conforme orientação da biblioteca e por isso tinha me esquecido dele.
Veja como ficou o teste com o fireEvent:
...
render(
<ImageLoader
src="image-teste.png"
fallbackImage="fallback-image.png"
alt="image-test"
/>,
);
const imageRef = screen.getByAltText('image-test');
fireEvent.load(imageRef);
expect(imageRef).toHaveAttribute('src', 'image-teste.png');
});
Não houve mudanças significativas na escrita, mas continuei usando o padrão e me parece muito mais semântico pelo nome da função, correto?
O teste para caso de erro ficou assim:
...
render(
<ImageLoader
src="image-teste.png"
fallbackImage="fallback-image.png"
alt="image-test"
/>,
);
const imageRef = screen.getByAltText('image-test');
fireEvent.error(imageRef);
expect(imageRef).toHaveAttribute('src', 'fallback-image.png');
});
Então se você já usa a biblioteca de testes padrão do React e precisa simular um evento, use a função fireEvent
.
Agora se você usa o Enzyme, por exemplo, pode fazer uso do ReactTestUtils para resolver seus problemas de teste de eventos.
Espero que este pequeno artigo possa ter te ajudado!
Deixe um comentário caso você você tenha gostado do artigo ou se tiver alguma sugestão ou dúvida.
Me adicione também no Linkedin e me deixa uma mensagem lá. Gosto muito de trocar ideias sobre desenvolvimento usando o JavaScript.
Quer saber um pouco mais sobre a biblioteca React Testing Library? Veja este artigo do criador dela em comparação com o Enzyme.
Té mais!
Top comments (1)
🚀🚀🚀