Desenvolvimento de aplicações é uma área em constante expansão nos últimos anos, sobretudo na pandemia. Devido a este intenso crescimento, a demanda de desenvolvedores de todos os níveis têm crescido muito, em descompasso com a quantidade de profissionais qualificados. Nesse cenário, faz-se necessário não apenas ser capaz de escrever código, mas garantir que o código faz o que se propõe. Atestar essa qualidade. É em função disso que fiz esse guia, para apresentar algumas formas de fazer testes unitários.
A estrutura de teste
Para trazer o conteúdo de forma prática optei por utilizar a biblioteca Jest em exemplos. Minha escolha se deve ao fato de ser uma das ferramentas mais utilizadas na construção de testes unitários atualmente, à sua popularidade e ao alto percentual de adesão a frameworks baseados em Javascript.
Jest é uma biblioteca que oferece um ambiente no qual podemos desenvolver formas de verificar que determinado código em Javascript está tendo o comportamento esperado dentro do sistema em questão. A estrutura base consiste em 3 partes:
uma descrição da unidade que está sendo testada;
uma descrição da expectativa geral de funcionamento de um bloco; e por fim,
estruturamos as expectativas no decorrer do processamento do código.
Podemos observar cada parte no exemplo abaixo, respectivamente describe(), it(), expect().
describe("UNIT DESCRIPTION", () => {
it("block expectation description", () => {
expect().toBeValid();
});
});
Testando Funções
Com essas estruturas e seus usos em mente, trago um exemplo de como ficaria o teste de uma função feita em Javascript. A função em questão recebe um valor de email e o testa num padrão RegEx, retornando um booleano. Segue a função:
export const emailValidation = (value) => {
const pattern = /^([a-z\d.-]+)@([a-z\d-]+)\.([a-z]{2,8})(\.[a-z]{2,8})?$/;
return pattern.test(value);
};
Nesse caso, o funcionamento correto da função é retornar verdadeiro caso o parâmetro seja um email válido, e falso caso contrário. Seguindo essa linha de pensamento podemos verificar os valores de entrada e saída da seguinte forma:
describe("emailValidation", () => {
it("should return true if email is valid", () => {
const email = "valid@email.com";
expect(emailValidation(email)).toBeTruthy();
});
it("should return false if email is invalid", () => {
const email = "invalid/email.co@";
expect(emailValidation(email)).toBeFalsy();
});
});
No teste acima faço verificação das duas possibilidades, passando e-mails válidos e inválidos. O teste pode e deve cobrir a maior quantidade dentre as mais variadas possibilidades(undefined, null, string vazias, etc…), mas por hora observe a estrutura da expectativa nos diferentes casos. A lista de expectativas são predefinidas, mas com a variedade disponível não é comum ter dificuldade para suprir os casos de uso.
Testando Elementos no DOM
Mesmo sendo bastante completa, a biblioteca Jest pode ainda ser utilizada combinada com outras ferramentas para estender a cobertura de testes. O caso anterior verificava a lógica de funcionamento de uma função, mas há outras situações como por exemplo a verificação de elementos no DOM, que veremos a seguir. Abaixo temos um componente React Js, uma popup para fazer login:
import LogoImage from "../assets/images/logo.svg";
import { emailValidation } from "../utils/validation.js";
const LoginPopup = ({ loginFunction }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const didLogin = () => {
if (emailValidation(email)) {
loginFunction(email, password);
}
};
return (
<div>
<label htmlFor="email">
<h5>Email</h5>
</label>
<input type="text" onChange={(e) => setEmail(e.target.value)} value={email} id="email" />
<label htmlFor="password">
<h5>Password</h5>
</label>
<input type="password" onChange={(e) => setPassword(e.target.value)} value={password} id="password" />
<button onClick={() => didLogin()}>Confirmar</button>
<img
src={LogoImage}
style={{
margin: "10",
width: "40",
height: "40",
objectFit: "cover",
}}
alt="Logo"
/>
</div>
);
};
export default LoginPopup;
Para testar esse componente nos próximos exemplos faço uso de ferramentas de outra biblioteca de testes, nesse caso a ferramenta render() da Testing Library para React. Esta ferramenta serve para gerar uma renderização de avaliação dos elementos presentes nos componentes renderizados. E é aplicada da seguinte forma:
import { render } from "@testing-library/react";
import LoginPopup from "./LoginPopup";
describe("LoginPopup", () => {
it("should have a logo with specified style.", () => {
const { getByAltText } = getRenderer();
expect(getByAltText("Logo")).toHaveStyle({
margin: "10",
width: "40",
height: "40",
objectFit: "cover",
});
});
});
function getRenderer() {
return render(<LoginPopup />);
}
Tendo sido chamado a função render() conforme documentação da Testing Library(TL), podemos observar as 3 estruturas do Jest mantidas:
a unidade em teste - LoginPopup;
a descrição do teste - nesse caso verificando a existência de uma imagem logo a ser renderizada com uma estilização específica; e,
as expectativas que dão por confirmado o funcionamento correto, usando a biblioteca adicional(TL) para capturar o objeto alvo e o Jest para verificar os estilos.
No exemplo a seguir eu demonstro uma forma muito útil na hora de fazer testes repetidos. A estrutura .each([ ]), que serve para encadear testes iguais. Neste caso, uso para verificar que os headings “Email” e “Password” estão no DOM.
import { render } from "@testing-library/react";
import LoginPopup from "./LoginPopup";
describe("<LoginPopup />", () => {
it.each(["Email", "Password"])("should have a heading '%s'.", (expected) => {
const { getByRole } = getRenderer();
expect(getByRole("heading", {name: expected})).toBeInTheDocument();
});
});
function getRenderer() {
return render(<LoginPopup />);
}
Os termos dentro dessa estrutura, que podem ser únicos ou um array, são passados como parâmetro e são esperados na propriedade name dos títulos (tags h1,h2,h5...) e são representados através da variável expected. É válido explicitar que cada item dentro de .each([ ]) irá disparar um teste sob as mesmas condições e expectativas então cuidado na hora de avaliar quais testes você pode encadear.
Testando Interações no DOM
Por último apresento um exemplo um pouco mais elaborado com o uso da ferramenta userEvent, também da Testing Library, que simula as interações que um usuário possa ter com a tela em apresentação.
Neste caso, meu objetivo é verificar que determinada função de login é chamada quando o usuário digita um email válido e clica no botão “Confirmar”. O que deveria em seguida logar o usuário e disparar uma mudança na tela renderizada.
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import LoginPopup from "./LoginPopup";
describe("<LoginPopup />", () => {
it("should log user in and change rendering if typed valid email and clicked in button.", (expected) => {
const signInWithEmail = jest.fn()
const { getByRole, queryByRole } = getRenderer({signInWithEmail});
userEvent.type(getByRole("textbox"), "valid@email.com")
userEvent.click(getByRole("button"))
expect(signInWithEmail).toBeCalledTimes(1);
expect(queryByRole("textbox")).not.toBeInTheDocument();
});
});
function getRenderer({signInWithEmail}) {
return render(<LoginPopup loginFunction={signInWithEmail} />);
}
Para isto usei o mock de uma função gerada pelo Jest(jest.fn()) e passo para o componente como propriedade. Repare o percurso de passagem para o renderizador e depois para o componente. Depois o uso de ambos eventos digitação e clique. Aqui chamo atenção para a possibilidade de tornar a expectativa negativa com o .not(na última linha do bloco de teste). Note também que nesse teste foi preciso cumprir duas expectativas, uma quanto ao mock da função ser chamada corretamente e a segunda é sobre o campo de digitação não estar mais sendo renderizado no documento revelando uma mudança de tela.
É dessa forma que a lógica dos testes vai se construindo e conforme os testes vão crescendo, há a necessidade de refatorar ou remover testes mais antigos que possam estar sendo cobertos por testes mais atuais. Em conclusão, ressalto a importância de testes como esses. Se você ficou com alguma dúvida ou tem algum comentário, não hesite em compartilhar. ;)