DEV Community

Eduardo Henrique Gris
Eduardo Henrique Gris

Posted on

Biblioteca de componentes React e typescript, parte 3: testes unitários com Jest e testing library

Introdução

English version: React and typescript components lib, part 3: unit tests with Jest and testing library

Na parte três da série, será incrementada a lib com a definição de testes unitários usando Jest e testing library. A ideia é colocar mais a parte prática de adição nesse artigo, para quem quiser entender mais a fundo como funciona de forma geral escrevi o artigo Setup Jest, Babel e testing library para testes unitários em React referente a parte do setup e o artigo Testes unitários em React com Jest e testing library com o geral de como escrever e como funcionam os testes.

Motivo testes unitários

Dado que está sendo criada uma biblioteca de componentes, o principal intuito é garantir que cada componente a ser disponibilizado tenha sua integridade validada de forma isolada.

Libs

Jest: framework de testes criado pelo Facebook, de simples configuração e uso, que permite rodar testes de forma isolada
Testing library: lib leve que permite simular interações com os componentes da aplicação
jest-styled-components: lib que facilita os testes de componentes estilizados com styled-components

Setup Jest

Adição das principais libs para configurar o Jest:

yarn add jest jest-environment-jsdom @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript --dev

  • jest-environment-jsdom: simula um ambiente DOM como se estivesse no navegador para os testes
  • @babel/preset-env: permite usar a versão mais recente de javascript, sem precisar definir quais transformações de sintaxe são necessárias para ser compatível com o ambiente que vai ser utilizado
  • @babel/preset-react: permite compilar código React
  • @babel/preset-typescript: permite a transpilação de typescript
  • @babel/core: traz a base do Babel para a aplicação

No momento desse artigo gerou as seguintes versões:

"@babel/core": "^7.26.10", "@babel/preset-env": "^7.26.9", "@babel/preset-react": "^7.26.3", "@babel/preset-typescript": "^7.26.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0" 
Enter fullscreen mode Exit fullscreen mode

Vão ser adicionados dois arquivos de configuração na raiz do projeto:

  • babel.config.js
module.exports = { presets: [ ["@babel/preset-env", {targets: {node: "current"}}], "@babel/preset-typescript", "@babel/preset-react" ], }; 
Enter fullscreen mode Exit fullscreen mode

Onde estão sendo usadas as libs de preset adicionadas.

  • jest.config.js
module.exports = { testEnvironment: "jsdom", testMatch: [ "**/src/components/**/*.test.tsx", ], }; 
Enter fullscreen mode Exit fullscreen mode

Onde em testEnvironment está se definindo para usar o ambiente de teste jsdom, que vem da lib jest-environment-jsdom. E em testMatch está se definindo em que lugar vai se localizar os arquivos de teste, como os testes vão ser criados dentro da pasta de cada componente, foi definido o caminho acima.

Setup testing library

Adição das principais libs:

yarn add @testing-library/react @testing-library/dom @testing-library/jest-dom @types/react @types/react-dom @types/jest --dev

  • @testing-library/react: adiciona a testing-library para uso em aplicações React
  • @testing-library/dom: peer dependência da @testing-library/react
  • @testing-library/jest-dom: traz uma maior quantidade de matchers para os testes de Jest, fazendo eles mais declarativos
  • @types/react: definição de types para React
  • @types/react-dom: definição de types para react-dom
  • @types/jest: definição de types para jest

No momento desse artigo gerou as seguintes versões:

"@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", 
Enter fullscreen mode Exit fullscreen mode

Setup jest-styled-components

Em ambos artigos de referência que coloquei na introdução, não foi abordado sobre essa lib, então vou falar um pouco sobre o que ela faz nesse artigo.
Essa lib busca facilitar a realização de testes para componentes que usam styled-components. Relembrando os componentes que estão criados na lib:

  • Text.tsx
import React from "react"; import styled from "styled-components"; export interface TextProps { children: React.ReactNode; color?: string; weight?: "normal" | "bold"; fontWeight?: number; fontSize?: string; fontFamily?: string; } export interface StyledTextProps { $color?: string; $weight?: "normal" | "bold"; $fontWeight?: number; $fontSize?: string; $fontFamily?: string; } export const StyledText = styled.span<StyledTextProps>` color: ${(props) => (props.$color ? props.$color : "#000")}; font-size: ${(props) => (props.$fontSize ? props.$fontSize : "16px")}; font-weight: ${(props) => props.$fontWeight ? props.$fontWeight : props.$weight ? props.$weight : "normal"}; font-family: ${(props) => (props.$fontFamily ? props.$fontFamily : "Arial")}; `; const Text = ({ children, color, weight, fontWeight, fontSize, fontFamily, }: TextProps) => ( <StyledText $color={color} $weight={weight} $fontWeight={fontWeight} $fontSize={fontSize} $fontFamily={fontFamily} > {children} </StyledText> ); export default Text; 
Enter fullscreen mode Exit fullscreen mode
  • Tag.tsx
import React from "react"; import styled from "styled-components"; import Text from "../Text/Text"; export interface TagProps { type?: "default" | "success" | "alert" | "error"; text: string; textColor?: string; textWeight?: "normal" | "bold"; textFontWeight?: number; textFontSize?: string; textFontFamily?: string; backgroundColor?: string; format?: "default" | "semiRounded" | "rounded"; borderRadius?: string; size?: "small" | "medium" | "large"; padding?: string; } export interface StyledTagProps { $type?: "default" | "success" | "alert" | "error"; $textColor?: string; $textWeight?: "normal" | "bold"; $textFontWeight?: number; $textFontSize?: string; $textFontFamily?: string; $backgroundColor?: string; $format?: "default" | "semiRounded" | "rounded"; $borderRadius?: string; $size?: "small" | "medium" | "large"; $padding?: string; } export const StyledTag = styled.div<StyledTagProps>` border: none; padding: ${(props) => props.$padding ? props.$padding : props.$size === "large" ? "15px 20px" : props.$size === "medium" ? "10px 12px" : "7px"}; background-color: ${(props) => props.$backgroundColor ? props.$backgroundColor : props.$type === "error" ? "#e97451" : props.$type === "alert" ? "#f8de7e" : props.$type === "success" ? "#50c878" : "#d3d3d3"}; pointer-events: none; border-radius: ${(props) => props.$borderRadius ? props.$borderRadius : props.$format === "rounded" ? "30px" : props.$format === "semiRounded" ? "5px" : "0"}; width: fit-content; `; const Tag = ({ text, type, textColor, textWeight, textFontWeight, textFontSize, textFontFamily, backgroundColor, format, borderRadius, size, padding, }: TagProps) => ( <StyledTag data-testid="tag" $type={type} $backgroundColor={backgroundColor} $format={format} $borderRadius={borderRadius} $size={size} $padding={padding} > <Text color={textColor || "#fff"} weight={textWeight} fontWeight={textFontWeight} fontSize={textFontSize} fontFamily={textFontFamily} > {text} </Text>  </StyledTag> ); export default Tag; 
Enter fullscreen mode Exit fullscreen mode

Nos dois componentes as propriedades de estilização são definidas usando styled-componentes: StyledText e StyledTag, onde tem algumas propriedades default, se as props não forem passadas para os componentes, ou varia as propriedades se forem passadas props. A lib do jest-styled-components fornece um matcher chamado toHaveStyleRule, que valida dado uma props passada, se via styled-componentes está sendo obtida a estilização esperada para o componente.

Adição da lib:

yarn add jest-styled-components --dev

No momento desse artigo gerou a seguinte versão:

"jest-styled-components": "^7.2.0" 
Enter fullscreen mode Exit fullscreen mode

Adição testes

Uma vez que as libs foram adicionadas e configuradas, serão adicionados os testes unitários para os dois componentes, dentro da pasta onde é definido o componente em si.

  • Text.test.tsx
import React from "react"; import "@testing-library/jest-dom"; import "jest-styled-components"; import { render, screen } from "@testing-library/react"; import Text from "./Text"; describe("<Text />", () => { it("should render component with default properties", () => { render(<Text>Text</Text>);  const element = screen.getByText("Text"); expect(element).toBeInTheDocument(); expect(element).toHaveStyleRule("color", "#000"); expect(element).toHaveStyleRule("font-size", "16px"); expect(element).toHaveStyleRule("font-weight", "normal"); }); it("should render component with custom color", () => { render(<Text color="#fff">Text</Text>);  expect(screen.getByText("Text")).toHaveStyleRule("color", "#fff"); }); it("should render component with bold weight", () => { render(<Text weight="bold">Text</Text>);  expect(screen.getByText("Text")).toHaveStyleRule("font-weight", "bold"); }); it("should render component with custom weight", () => { render(<Text fontWeight={500}>Text</Text>);  expect(screen.getByText("Text")).toHaveStyleRule("font-weight", "500"); }); it("should render component with custom font size", () => { render(<Text fontSize="20px">Text</Text>);  expect(screen.getByText("Text")).toHaveStyleRule("font-size", "20px"); }); it("should render component with custom font family", () => { render(<Text fontFamily="TimesNewRoman">Text</Text>);  expect(screen.getByText("Text")).toHaveStyleRule( "font-family", "TimesNewRoman", ); }); }); 
Enter fullscreen mode Exit fullscreen mode
  • Tag.test.tsx
import React from "react"; import "@testing-library/jest-dom"; import "jest-styled-components"; import { render, screen, within } from "@testing-library/react"; import Tag from "./Tag"; describe("<Tag />", () => { it("should render component with default properties", () => { render(<Tag text="Tag" />); const element = screen.getByTestId("tag"); expect(element).toBeInTheDocument(); expect(element).toHaveStyleRule("background-color", "#d3d3d3"); expect(element).toHaveStyleRule("border-radius", "0"); expect(element).toHaveStyleRule("padding", "7px"); expect(within(element).getByText("Tag")).toHaveStyleRule("color", "#fff"); }); it("should render component with success type", () => { render(<Tag text="Tag" type="success" />); const element = screen.getByTestId("tag"); expect(element).toHaveStyleRule("background-color", "#50c878"); expect(within(element).getByText("Tag")).toHaveStyleRule("color", "#fff"); }); it("should render component with alert type", () => { render(<Tag text="Tag" type="alert" />); const element = screen.getByTestId("tag"); expect(element).toHaveStyleRule("background-color", "#f8de7e"); expect(within(element).getByText("Tag")).toHaveStyleRule("color", "#fff"); }); it("should render component with error type", () => { render(<Tag text="Tag" type="error" />); const element = screen.getByTestId("tag"); expect(element).toHaveStyleRule("background-color", "#e97451"); expect(within(element).getByText("Tag")).toHaveStyleRule("color", "#fff"); }); it("should render component with custom background color", () => { render(<Tag text="Tag" backgroundColor="#fff" />); expect(screen.getByTestId("tag")).toHaveStyleRule( "background-color", "#fff", ); }); it("should render component with semi rounded format", () => { render(<Tag text="Tag" format="semiRounded" />); expect(screen.getByTestId("tag")).toHaveStyleRule("border-radius", "5px"); }); it("should render component with rounded format", () => { render(<Tag text="Tag" format="rounded" />); expect(screen.getByTestId("tag")).toHaveStyleRule("border-radius", "30px"); }); it("should render component with custom border radius", () => { render(<Tag text="Tag" borderRadius="20px" />); expect(screen.getByTestId("tag")).toHaveStyleRule("border-radius", "20px"); }); it("should render component with medium size", () => { render(<Tag text="Tag" size="medium" />); expect(screen.getByTestId("tag")).toHaveStyleRule("padding", "10px 12px"); }); it("should render component with large size", () => { render(<Tag text="Tag" size="large" />); expect(screen.getByTestId("tag")).toHaveStyleRule("padding", "15px 20px"); }); it("should render component with custom size", () => { render(<Tag text="Tag" padding="20px 10px" />); expect(screen.getByTestId("tag")).toHaveStyleRule("padding", "20px 10px"); }); it("should render component with custom text color", () => { render(<Tag text="Tag" textColor="#000" />); const element = screen.getByTestId("tag"); expect(within(element).getByText("Tag")).toHaveStyleRule("color", "#000"); }); it("should render component with bold text font weight", () => { render(<Tag text="Tag" textWeight="bold" />); const element = screen.getByTestId("tag"); expect(within(element).getByText("Tag")).toHaveStyleRule( "font-weight", "bold", ); }); it("should render component with custom text font weight", () => { render(<Tag text="Tag" textFontWeight={200} />);  const element = screen.getByTestId("tag"); expect(within(element).getByText("Tag")).toHaveStyleRule( "font-weight", "200", ); }); it("should render component with custom text font size", () => { render(<Tag text="Tag" textFontSize="30px" />); const element = screen.getByTestId("tag"); expect(within(element).getByText("Tag")).toHaveStyleRule( "font-size", "30px", ); }); it("should render component with custom text font family", () => { render(<Tag text="Tag" textFontFamily="Times New Roman" />); const element = screen.getByTestId("tag"); expect(within(element).getByText("Tag")).toHaveStyleRule( "font-family", "Times New Roman", ); }); }); 
Enter fullscreen mode Exit fullscreen mode

Em ambos os arquivos de teste, o primeiro teste analisa as propriedades default do componente. Os outros testes validam as propriedades após passar props que impactam elas, seja de propriedades pré-definidas ou customizáveis.

package.json

No momento o package.json está da forma abaixo:

{ "name": "react-example-lib", "version": "0.2.0", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "types": "dist/index.d.ts", "files": [ "dist" ], "license": "MIT", "repository": { "type": "git", "url": "https://github.com/griseduardo/react-example-lib.git" }, "scripts": { "build": "rollup -c --bundleConfigAsCjs", "lint-src": "eslint src", "lint-src-fix": "eslint src --fix", "format-src": "prettier src --check", "format-src-fix": "prettier src --write" }, "devDependencies": { "@babel/core": "^7.26.10", "@babel/preset-env": "^7.26.9", "@babel/preset-react": "^7.26.3", "@babel/preset-typescript": "^7.26.0", "@eslint/js": "^9.19.0", "@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "11.1.6", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "eslint": "^9.19.0", "eslint-plugin-react": "^7.37.4", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-styled-components": "^7.2.0", "prettier": "^3.4.2", "react": "^19.0.0", "react-dom": "^19.0.0", "rollup": "^4.30.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-peer-deps-external": "^2.2.4", "styled-components": "^6.1.14", "typescript": "^5.7.3", "typescript-eslint": "^8.23.0" }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "styled-components": "^6.1.14" } } 
Enter fullscreen mode Exit fullscreen mode

Será adicionado um script para executar os testes test, além disso vai ser mudada a versão para 0.3.0, uma vez que uma nova versão da lib será disponibilizada:

{ "name": "react-example-lib", "version": "0.3.0", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "types": "dist/index.d.ts", "files": [ "dist" ], "license": "MIT", "repository": { "type": "git", "url": "https://github.com/griseduardo/react-example-lib.git" }, "scripts": { "build": "rollup -c --bundleConfigAsCjs", "lint-src": "eslint src", "lint-src-fix": "eslint src --fix", "format-src": "prettier src --check", "format-src-fix": "prettier src --write", "test": "jest" }, "devDependencies": { "@babel/core": "^7.26.10", "@babel/preset-env": "^7.26.9", "@babel/preset-react": "^7.26.3", "@babel/preset-typescript": "^7.26.0", "@eslint/js": "^9.19.0", "@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "11.1.6", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "eslint": "^9.19.0", "eslint-plugin-react": "^7.37.4", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-styled-components": "^7.2.0", "prettier": "^3.4.2", "react": "^19.0.0", "react-dom": "^19.0.0", "rollup": "^4.30.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-peer-deps-external": "^2.2.4", "styled-components": "^6.1.14", "typescript": "^5.7.3", "typescript-eslint": "^8.23.0" }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "styled-components": "^6.1.14" } } 
Enter fullscreen mode Exit fullscreen mode

Arquivo CHANGELOG

No momento o CHANGELOG.md está da forma abaixo:

## 0.2.0 _Fev. 24, 2025_ - setup typescript-eslint and prettier - add custom rules ## 0.1.0 _Jan. 29, 2025_ - initial config 
Enter fullscreen mode Exit fullscreen mode

Como uma nova versão será disponibilizada, será adicionado sobre o que foi modificado no arquivo:

## 0.3.0 _Mar. 24, 2025_ - setup jest and testing-library - add components tests ## 0.2.0 _Fev. 24, 2025_ - setup typescript-eslint and prettier - add custom rules ## 0.1.0 _Jan. 29, 2025_ - initial config 
Enter fullscreen mode Exit fullscreen mode

Estrutura de pastas

A estrutura de pastas está da seguinte forma, sendo que além da modificação de alguns arquivos, foi adicionado dois arquivos de configuração de testes e dois arquivos novos de teste de componentes:

Image description

Publicação nova versão

Primeiro passo a ser realizado é ver se a execução do rollup ocorre com sucesso. Para isso será executado o yarn build no terminal, que foi definido em package.json.
Executando com sucesso, é realizar a publicação da nova versão da lib: npm publish --access public

Conclusão

A ideia desse artigo foi fazer o setup e criar testes unitários para os componentes, usando Jest, testing-library e jest-styled-components, com uma visão mais prática de aplicação. Para entender como funciona a estrutura e execução dos testes, os artigos passados na introdução busca mostrar de forma mais detalhada esses pontos.
Segue o repositório no github e a lib no npmjs com as novas modificações.

Top comments (0)