├── .github └── workflows │ └── unit-test.yml ├── .gitignore ├── README.md ├── __mocks__ └── axios.js ├── package-lock.json ├── package.json ├── src ├── index.js └── promiseTestModule.js └── tests ├── mock.test.js └── spy.test.js /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: Testes unitários 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | inputs: 8 | jest-cli: 9 | description: 'Opção de Jest CLI' 10 | required: false 11 | default: '' 12 | 13 | jobs: 14 | unit-test: 15 | 16 | runs-on: ubuntu-18.04 17 | 18 | steps: 19 | - name: Checkout do repositório 20 | uses: actions/checkout@v2 21 | - name: Setup do Node.js 22 | uses: actions/setup-node@v2.1.1 23 | - name: Instalar as dependências do projeto 24 | run: npm ci 25 | - name: Executar os testes de unidade ${{ github.event.inputs.jest-cli }} 26 | run: npm test -- ${{ github.event.inputs.jest-cli }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dublês de teste com [Jest](https://www.npmjs.com/package/jest) [![Testes unitários](https://github.com/PauloGoncalvesBH/dubles-de-teste-com-jest/workflows/Testes%20unit%C3%A1rios/badge.svg)](https://github.com/PauloGoncalvesBH/dubles-de-teste-com-jest/actions) 2 | 3 |
4 | Esse material é quase de graça, basta deixar uma star ⭐ no repositório. 5 |
6 | 7 | ## Sobre 8 | 9 | Esse projeto é para servir de material pessoal de consulta de como implementar dublês de testes utilizando Jest. 10 | 11 | > **Observação:** As asserções não seguem as boas práticas de cada dublê pois foi preciso validar se as alterações de comportamento estão funcionando. 12 | 13 | Os testes foram implementados em [`/tests`](/tests), consumindo os métodos de [`/src`](/src) e com mock manual em [`/__mocks__`](/__mocks__). 14 | 15 | ## Dublês implementados 16 | 17 | - Mock, em [./tests/mock.test.js](tests/mock.test.js) 18 | - [`Manual Mock`](https://jestjs.io/docs/en/manual-mocks) - Mockando um módulo manualmente 19 | - [`mockResolvedValue()`](https://jestjs.io/docs/en/mock-functions#mocking-modules) - Resolvendo uma Promise e mockando um módulo 20 | - [`mockRejectedValue()`](https://jestjs.io/docs/en/mock-function-api#mockfnmockrejectedvaluevalue) - Rejeitando uma Promise e mockando um módulo 21 | - [`mockFn.mockImplementation(fn)`](https://jestjs.io/docs/en/mock-function-api#mockfnmockimplementationfn) - Mockando um método 22 | - [`mockFn.mockImplementationOnce(fn)`](https://jestjs.io/docs/en/mock-function-api#mockfnmockimplementationoncefn) - Mockando um método na primeira 23 | e segunda chamada 24 | - Spy, em [./tests/spy.test.js](tests/spy.test.js) 25 | - [`jest.spyOn(object, methodName)`](https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname) - Visualizando comportamento interno de um método 26 | - [`jest.spyOn(object, methodName, accessType?)`](https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname-accesstype) - Visualizando comportamento interno de um método com tipo de acesso 27 | 28 | ## Execução 29 | 30 | Instale as dependências e execute os testes com: 31 | ``` 32 | npm it 33 | ``` 34 | 35 | ## Materiais de apoio 36 | 37 | - [Estudo de como é a implementação interna de um spy, mock, expect e estrutura dos testes](https://github.com/PauloGoncalvesBH/my-framework-test) 38 | 39 | ### Documentação do Jest 40 | - [Jest Object](https://jestjs.io/docs/en/jest-object) 41 | - [Expect](https://jestjs.io/docs/en/expect) 42 | - [Manual Mocks](https://jestjs.io/docs/en/manual-mocks) 43 | - [Mock Functions](https://jestjs.io/docs/en/mock-function-api) 44 | 45 | ## Outros textos 46 | 47 | - [Test Doubles (Mocks, Stubs, Fakes, Spies e Dummies) - PT-BR](https://medium.com/rd-shipit/test-doubles-mocks-stubs-fakes-spies-e-dummies-a5cdafcd0daf) 48 | - [Test Doubles - Fakes, Mocks and Stubs - EN](https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da) 49 | - [Vamos falar de dublês de teste (Test Double) para testes unitários](https://dev.to/henriquepalote/vamos-falar-de-dubles-de-teste-test-double-para-testes-unitarios-2725) 50 | -------------------------------------------------------------------------------- /__mocks__/axios.js: -------------------------------------------------------------------------------- 1 | // mock de módulo utilizado em mock.test.js e método getUserByEmail 2 | 3 | module.exports = { 4 | get: jest.fn((rota, { params }) => Promise.resolve({ 5 | data: { 6 | quantidade: 5, 7 | usuarios: [ 8 | { 9 | nome: 'Fulano', 10 | email: params.email 11 | } 12 | ] 13 | } 14 | })) 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dubles-de-teste-com-jest", 3 | "author": "Paulo Gonçalves (https://www.linkedin.com/in/paulo-goncalves/)", 4 | "license": "MIT", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/PauloGoncalvesBH/dubles-de-teste-com-jest.git" 9 | }, 10 | "scripts": { 11 | "test": "jest --collectCoverage true --silent", 12 | "test:watch": "jest --watch" 13 | }, 14 | "dependencies": { 15 | "axios": "^0.21.1", 16 | "jest": "^26.6.3" 17 | }, 18 | "jest": { 19 | "verbose": true, 20 | "testMatch": [ 21 | "**/*.test.js" 22 | ], 23 | "collectCoverageFrom": [ 24 | "src/*.js" 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | const promiseTest = require('./promiseTestModule') 4 | 5 | // métodos utilizados em spy.test.js 6 | 7 | function exception () { 8 | console.error('Testes unitários em JS\t\nOcorreu um problema desconhecido.\n') 9 | process.exit(1) 10 | } 11 | 12 | const pessoa = { 13 | get nacionalidade () { 14 | return 'brasileiro' 15 | } 16 | } 17 | 18 | // métodos utilizados em mock.test.js 19 | 20 | const getUserByEmail = async (email) => { 21 | const { data } = await axios.get('https://serverest.dev/usuarios', { 22 | params: { 23 | email 24 | } 25 | }) 26 | return data 27 | } 28 | 29 | const getQuantidadeFromUserByEmail = async (email) => { 30 | const { quantidade } = await getUserByEmail(email) 31 | return quantidade 32 | } 33 | 34 | const consumirPromise = resolver => { 35 | promiseTest(resolver) 36 | .then((e) => console.log(e)) 37 | .catch((error) => console.error(error)) 38 | } 39 | 40 | module.exports = { 41 | exception, 42 | pessoa, 43 | getUserByEmail, 44 | getQuantidadeFromUserByEmail, 45 | consumirPromise 46 | } 47 | -------------------------------------------------------------------------------- /src/promiseTestModule.js: -------------------------------------------------------------------------------- 1 | 2 | function promiseTest(resolver) { 3 | return new Promise((resolve, reject) => { 4 | if (resolver) 5 | resolve('Promise resolvida') 6 | reject('Deu erro') 7 | }) 8 | } 9 | 10 | module.exports = promiseTest -------------------------------------------------------------------------------- /tests/mock.test.js: -------------------------------------------------------------------------------- 1 | const src = require('../src') 2 | const axios = require('axios') 3 | 4 | jest.mock('../src/promiseTestModule') 5 | 6 | describe('Mock', () => { 7 | beforeEach(() => jest.clearAllMocks()) 8 | 9 | /* 10 | Mockando um módulo manualmente 11 | Manual Mocks - https://jestjs.io/docs/en/manual-mocks 12 | */ 13 | test('Manual Mock - Mockando um módulo manualmente', async () => { 14 | const user = await src.getUserByEmail('teste@qa.com') 15 | 16 | expect(axios.get).toHaveBeenCalledTimes(1) 17 | expect(axios.get).toHaveBeenCalledWith('https://serverest.dev/usuarios', { 18 | params: { 19 | email: 'teste@qa.com' 20 | } 21 | }) 22 | expect(user.quantidade).toBe(5) 23 | expect(user.usuarios[0].email).toBe('teste@qa.com') 24 | }) 25 | 26 | /* 27 | Resolvendo uma Promise e mockando um módulo 28 | Mocking Modules - https://jestjs.io/docs/en/mock-functions#mocking-modules 29 | */ 30 | test('mockResolvedValue() - Resolvendo uma Promise e mockando um módulo', async () => { 31 | axios.get.mockResolvedValueOnce({ 32 | data: { 33 | quantidade: 102030, 34 | usuarios: [ 35 | { 36 | nome: 'Fulano da Silva', 37 | email: 'fulano.teste@qa.com' 38 | } 39 | ] 40 | } 41 | }) 42 | 43 | const user = await src.getUserByEmail('fulano.teste@qa.com') 44 | 45 | expect(axios.get).toHaveBeenCalledTimes(1) 46 | expect(axios.get).toHaveBeenCalledWith('https://serverest.dev/usuarios', { 47 | params: { 48 | email: 'fulano.teste@qa.com' 49 | } 50 | }) 51 | expect(user.quantidade).toBe(102030) 52 | expect(user.usuarios[0].email).toBe('fulano.teste@qa.com') 53 | }) 54 | 55 | /* 56 | Rejeitando uma Promise e mockando um módulo 57 | Mocking Modules - https://jestjs.io/docs/en/mock-function-api#mockfnmockrejectedvaluevalue 58 | */ 59 | test('mockRejectedValue() - Rejeitando uma Promise e mockando um módulo', () => { 60 | const promiseTest = require('../src/promiseTestModule') 61 | promiseTest.mockRejectedValueOnce('Promise mockada') 62 | 63 | src.consumirPromise(true) 64 | }) 65 | 66 | /* 67 | Mockando um método 68 | mockFn.mockImplementation(fn) - https://jestjs.io/docs/en/mock-function-api#mockfnmockimplementationfn 69 | */ 70 | test('mockFn.mockImplementation(fn) - Mockando um método', async () => { 71 | const mock = jest.spyOn(src, 'getQuantidadeFromUserByEmail').mockImplementationOnce(() => 99999) 72 | 73 | const quantidadeDeUsuarios = await src.getQuantidadeFromUserByEmail('teste@qa.com') 74 | 75 | expect(mock).toHaveBeenCalledTimes(1) 76 | expect(mock).toHaveBeenCalledWith('teste@qa.com') 77 | expect(quantidadeDeUsuarios).toBe(99999) 78 | }) 79 | 80 | /* 81 | Mockando um método na primeira e segunda chamada 82 | mockFn.mockImplementationOnce(fn) - https://jestjs.io/docs/en/mock-function-api#mockfnmockimplementationoncefn 83 | */ 84 | test('mockFn.mockImplementationOnce(fn) - Mockando um método na primeira e segunda chamada', async () => { 85 | const mock = jest 86 | .spyOn(src, 'getQuantidadeFromUserByEmail') 87 | .mockImplementationOnce(() => 654321) 88 | .mockImplementationOnce(() => 123456) 89 | 90 | const quantidadeDeUsuariosPrimeiraChamada = await src.getQuantidadeFromUserByEmail('teste@qa.com') 91 | const quantidadeDeUsuariosSegundaChamada = await src.getQuantidadeFromUserByEmail('teste@qa.com') 92 | const quantidadeDeUsuariosTerceiraChamada = await src.getQuantidadeFromUserByEmail('teste@qa.com') 93 | 94 | expect(mock).toHaveBeenCalledTimes(3) 95 | expect(quantidadeDeUsuariosPrimeiraChamada).toBe(654321) 96 | expect(quantidadeDeUsuariosSegundaChamada).toBe(123456) 97 | expect(quantidadeDeUsuariosTerceiraChamada).toBe(5) 98 | }) 99 | }) 100 | -------------------------------------------------------------------------------- /tests/spy.test.js: -------------------------------------------------------------------------------- 1 | const src = require('../src') 2 | 3 | describe('Spy', () => { 4 | /* 5 | Visualizando comportamento interno de um método 6 | jest.spyOn(object, methodName) - https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname 7 | */ 8 | test('jest.spyOn(object, methodName) - Visualizando comportamento interno de um método', () => { 9 | const consoleSpy = jest.spyOn(console, 'error') 10 | const exitMock = jest.spyOn(process, 'exit').mockImplementation() 11 | 12 | src.exception() 13 | 14 | expect(consoleSpy).toHaveBeenCalledTimes(1) 15 | expect(consoleSpy).toHaveBeenCalledWith('Testes unitários em JS\t\nOcorreu um problema desconhecido.\n') 16 | expect(exitMock).toHaveBeenCalledWith(1) 17 | }) 18 | 19 | /* 20 | Visualizando comportamento interno de um método com tipo de acesso 21 | jest.spyOn(object, methodName, accessType?) - https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname-accesstype 22 | */ 23 | test('jest.spyOn(object, methodName, accessType?) - Visualizando comportamento interno de um método com tipo de acesso', () => { 24 | const spy = jest.spyOn(src.pessoa, 'nacionalidade', 'get') 25 | 26 | const nacionalidade = src.pessoa.nacionalidade 27 | 28 | expect(spy).toHaveBeenCalled() 29 | expect(nacionalidade).toBe('brasileiro') 30 | }) 31 | }) 32 | --------------------------------------------------------------------------------