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"
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"
],
};
Onde estão sendo usadas as libs de preset adicionadas.
- jest.config.js
module.exports = {
testEnvironment: "jsdom",
testMatch: [
"**/src/components/**/*.test.tsx",
],
};
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",
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;
- 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;
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"
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",
);
});
});
- 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",
);
});
});
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"
}
}
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"
}
}
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
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
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:
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)