Criado por Adriano de Azevedo usando reveal.js
E as vezes eu não consigo responder todo mundo
Escrever testes para Front-end sempre foi um caos desgraçado. Nunca existiu um padrão, ou uma ferramenta boa e simples de se configurar. Muitas vezes, testes para front-end eram deixado de lado pela falta de clareza de como se fazer.Veja detalhes aqui
Jest é um framework de teste em JavaScript [...] Ele permite que você escreva testes com uma API acessível, familiar e rica em recursos [...] está bem documentado, requer pouca configuração [...]Veja detalhes aqui
Jest torna os testes agradavéis.
O jest é uma ferramenta completa
Ferramentas para isso (testar) sempre estiveram por aí, tais como QUnit, Jasmine, Mocha, Karma, Chai, Sinon [...] fazer todas essas ferramentas trabalharem juntas, olha, levava dias para deixar do jeito certo [...]Veja detalhes aqui
(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$
├── __tests__
│ └── component.spec.js # test
│ └── anything # test
├── package.json # not test
├── foo.test.js # test
├── bar.spec.jsx # test
└── component.js # not test
Quando estamos escrevendo testes, geralmente precisamos verificar se os valores satisfazem certas condições. O Jest faz isso por meio da função `expect` usando "matchers"
test('dois mais dois é quatro', () => {
expect(2 + 2).toBe(4);
});
test('dois mais dois não é 5', () => {
expect(2 + 2).not.toBe(5);
});
test('atribuição de objeto', () => {
const data = {one: 1};
data['two'] = 2;
// devemos usar toEqual para testar objetos
// toBe utiliza `Object.is` para
// testar a igualdade extata.
expect(data).toEqual({one: 1, two: 2});
});
test('nulo', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('dois mais dois', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
// toBe e toEqual são equivalentes para números
expect(value).toBe(4);
expect(value).toEqual(4);
});
describe("2 + 2", () => {
test('dois mais dois é quatro', () => {
expect(2 + 2).toBe(4);
});
test('dois mais dois não é 5', () => {
expect(2 + 2).not.toBe(5);
});
});
describe("2 + 2", () => {
test.skip('dois mais dois é quatro', () => {
// teste quebrado de propósito
expect(2 + 2).toBe(5);
});
test('dois mais dois não é 5', () => {
expect(2 + 2).not.toBe(5);
});
});
Muitas vezes ao escrever testes você tem algum trabalho de configuração que precisa acontecer antes ou depois de executar testes
// executa em todos os testes do arquivo
beforeEach(() => {
initializeCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
describe('matching cities to foods', () => {
// executa apenas para testes neste bloco
beforeEach(() => {
initializeFoodDatabase();
});
test('Vienna <3 sausage', () => {
expect(
isValidCityFoodPair('Vienna', 'Wiener Schnitzel')
).toBe(true);
});
});
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 1 - afterAll
[...] uma métrica quantitativa, visa medir quanto (%) do software é coberto/exercitado ao executar um determinado conjunto de casos de testes.Veja detalhes aqui
verifica quantas funções do código são chamadas
verifica quantas instruções do código são executadas
verifica se cada ramificação de cada estrutura de controle (incluindo if/else, switch case, for, while) é executada
verifica se cada sub-expressão booleana são avaliadas ambas como verdadeiras e falsas
quanto mais responsabilidade existir em uma função, mais dificil vai ser para testar
não há garantia que o código será livre de defeito por ter uma cobertura alta
cobertura alta de código não é approve automático de PR
O Codecov faz um análise estática no nosso coverage e conseguimos tirar informações relevantes disso
Nós podemos, por exemplo, bloquear a CI e evitar que o PR seja mergeado caso ele não atenda um valor (target) de coverage do código novo
# inicia um novo projeto
npm init -y
# adiciona o jest como dependência do projeto
npm install --save-dev jest
package.json
{
"scripts": {
"test": "jest"
}
}
// src/sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;
// src/sum.test.js
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
# execute no seu terminal
npm run test
# PASS src/sum.test.js
# ✓ adds 1 + 2 to equal 3 (2 ms)
module.exports
e
require
?
import
e export
?
// src/sum.js
function sum(a, b) {
return a + b;
}
export default sum;
// src/sum.test.js
import sum from './sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
Por meio de plugins, o babel transpila código JavaScript moderno em uma versão compatível com versões anteriores do JavaScript em navegadores ou ambientes atuais e mais antigos.
# adiciona o babel como dependência do projeto
npm install --save-dev babel-jest
npm install --save-dev @babel/core
npm install --save-dev @babel/preset-env
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}]
],
};
# execute no seu terminal
npm run test
# PASS src/sum.test.js
# ✓ adds 1 + 2 to equal 3 (2 ms)
# adiciona suporte ao TypeScript via babel
npm install --save-dev @babel/preset-typescript
npm install --save-dev @types/jest
npm install --save-dev @types/node
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript'
],
};
// src/sum.ts
function sum(a: number, b: number) {
return a + b;
}
export default sum;
// src/sum.test.ts
import sum from './sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
# execute no seu terminal
npm run test
# PASS src/sum.test.ts
# ✓ adds 1 + 2 to equal 3 (2 ms)
O suporte para TypeScript via Babel é apenas para transpilação.
O Jest não irá checar a tipagem dos seus testes enquanto eles são executados
# adiciona as dependências do React
npm install --save react
npm install --save react-dom
npm install --save-dev @babel/preset-react
# adiciona suporte ao TypeScript
npm install --save-dev @types/react
npm install --save-dev @types/react-dom
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
'@babel/preset-react'
],
};
É usado pelo Jest para emular um navegador web durante a execução dos testes.
# dependencias necessarias para usar React Testing Library
npm install --save-dev @testing-library/jest-dom
npm install --save-dev @testing-library/react
npm install --save-dev @testing-library/user-event
// jest-setup.ts
import '@testing-library/jest-dom';
# se você estiver usando o npm
npm run test -- --watch
# se voce estiver usando o yarn
yarn run test --watch
npm run test ./src/hello.test.ts
npm run test -- --coverage
# o coverage gera uma pasta chamada coverage
npm run test -- --coverage
# dentro da pasta coverage vai existir uns htmls
cd coverage/lcov-report
# inicie um servidor estático
npx serve
# forma como o jest é executado na CI
CI=true npm run test -- --coverage
# forma como o jest é executado na CI
CI=true npm run test -- --coverage --color
npm run test -- --verbose
// setABTest.ts
const setABTest = (percentage: number) => {
const newTest = Math.floor(Math.random() * 100) + 1;
let cookieVal = "";
if (newTest <= percentage) {
cookieVal = "piloto";
} else if (newTest <= percentage * 2) {
cookieVal = "controle";
} else {
cookieVal = "fora do teste";
}
return cookieVal;
};
export default setABTest;
// setABTest.spec.ts
import setABTest from './setABTest';
test('deve retornar piloto', () => {
expect(setABTest(50)).toBe('piloto')
})
// setABTest.spec.ts
afterEach(() => {
jest.spyOn(global.Math, "random").mockRestore();
});
test("deve retornar piloto", () => {
jest.spyOn(global.Math, "random").mockReturnValue(0.3);
expect(setABTest(40)).toBe("piloto");
});
test("deve retornar controle", () => {
jest.spyOn(global.Math, "random").mockReturnValue(0.6);
expect(setABTest(50)).toBe("controle");
});
// timerGame.ts
function timerGame(callback) {
setTimeout(() => {
callback("algum valor aqui");
}, 6000);
}
export default timerGame;
// timerGame.spec.ts
import timer from './timerGame';
jest.useFakeTimers();
test("executa callback daqui 6 segundos", () => {
const mock = jest.fn();
timer(mock);
jest.advanceTimersByTime(1000);
expect(mock).not.toHaveBeenCalled();
jest.advanceTimersByTime(5000);
expect(mock).toHaveBeenCalledWith('algum valor aqui');
});
// src/utils/useAuth.ts
const useAuth = () => {
// aqui vai bater na rede pra buscar o usuário
// exemplo de retorno
return {
isAuthenticated: false,
user: null,
};
};
export default useAuth;
Ou seja, se está dificil testar é um forte indício de que há algo errado com seu código
npm install --save @testing-library/react-hooks
Isso pode vazar o escopo do seu teste e afetar outros casos de teste
Dependendo de onde você colocar isso, vários casos de teste podem quebrar
Se você não fizer isso e seu teste falhar vai ser um inferno encontrar o que causou o erro
Quem revisa o PR também é responsável pela qualidade do código e dos testes. Então façam code-reviews profundos
É impossível garantir qualidade em um PR de 4k de linhas
Aqui temos várias pessoas da nossa equipe que podem te apoiar
Afinal você nunca vai ter tempo pra parar e escrever testes. Faça isso parte da sua rotina
Seu/sua PM não sabe quanto trabalho da pra fazer isso
Não deixe que o medo da sua pipeline falhar o impeça de testar
O criador da biblioteca React Testing Library criou um excelente artigo falando sobre os erros mais comuns que as pessoas cometem ao usar a lib
Veja detalhes aquiAqui tem um ótimo artigo explicando mais profundamente sobre como testar hooks
Veja detalhes aquiObrigado pelo suporte com a apresentação e por segurar as pontas no nosso time
Obrigado pela força com a revisão