Testando o código Node.js com Mocha e Chai

Introdução

Escrever testes de unidade é algo que iniciantes e engenheiros experientes normalmente adiam para fases posteriores de desenvolvimento, mas eles são essenciais para o desenvolvimento de software estável e robusto.

A premissa básica de desenvolvimento orientado a testes (TDD) está escrevendo seus testes antes mesmo de você começar a codificar. Esse é um grande objetivo pelo qual se esforçar, mas é preciso muita disciplina e planejamento quando você tenta seguir seus princípios! Para tornar todo esse processo muito mais fácil, você pode recorrer a estruturas de testes e asserções fáceis de usar e poderosas, como ágata e Chai.

Neste artigo, começaremos apresentando essas duas bibliotecas e, em seguida, mostraremos como usá-las juntas para criar rapidamente testes de unidade legíveis e funcionais.

Chai

Chai é uma biblioteca de asserções que fornece tanto o BDD (desenvolvimento orientado a comportamento) e TDD (desenvolvimento orientado a testes) estilos de programação para testar código e deve ser combinado com uma biblioteca de testes que permite organizar testes. É muito comumente combinado com Mocha.

Possui três APIs principais:

  • should
  • expect
  • assert

var.should.equal(var2)


expect.var.to.be.equal(var2)


assert.equal(var1, var2)

Ao longo deste artigo, focaremos no estilo BDD usando Chai’s expect interface, embora usar outras interfaces/estilos de acordo com sua intuição seja perfeitamente aceitável. O assert interface é a estrutura de declaração TDD mais comum.

expect usa uma API de linguagem muito natural para escrever suas asserções, o que tornará seus testes mais fáceis de escrever e melhorar mais tarde. Isso é feito encadeando getters para criar e executar a asserção, facilitando a tradução dos requisitos em código:

let user = {name: 'Scott'};


expect(user).to.have.property('name');

Observação: Viu como você pode ler a afirmação em uma linguagem natural e entender o que está acontecendo? Essa é uma das principais vantagens de usar uma biblioteca de asserções como a Chai!

Mais alguns exemplos desses getters são:

  • to
  • be
  • is
  • and
  • has
  • have

Muitos desses getters podem ser encadeados e usados ​​com métodos de asserção como true, ok, exist e empty para criar algumas asserções complexas em apenas uma linha:

"use strict";

const expect = require('chai').expect;


expect({}).to.exist;
expect(26).to.equal(26);
expect(false).to.be.false;
expect('hello').to.be.string;


expect([1, 2, 3]).to.not.be.empty;


expect([1, 2, 3]).to.have.length.of.at.least(3);

Observação: Uma lista completa dos métodos disponíveis pode ser encontrada SUA PARTICIPAÇÃO FAZ A DIFERENÇA.

Você também pode querer verificar a lista de disponíveis plugins para Chai. Isso torna muito mais fácil testar recursos mais complexos.

Tire chai-http por exemplo, que é um plugin que ajuda a testar rotas de servidor:

"use strict";

const chai = require('chai');
const chaiHttp = require('chai-http');

chai.use(chaiHttp);

chai.request(app)
    .put('/api/auth')
    .send({username: '[email protected]', password: 'abc123'})
    .end(function(err, res) {
        expect(err).to.be.null;
        expect(res).to.have.status(200);
    });

Organizando casos de teste com Mocha – description() e it()

Mocha é uma estrutura de teste para Node que oferece flexibilidade para executar código assíncrono (ou síncrono) serialmente. Quaisquer exceções não detectadas são mostradas junto com o caso de teste em que foram lançadas, facilitando a identificação exata do que falhou e por quê.

É aconselhável instalar o Mocha globalmente:

$ npm install mocha -g

Você vai querer que seja uma instalação global, já que o mocha O comando é usado para executar os testes do projeto em seu diretório local.

O que esse trecho de código faz?

it() deve retornar X. it() define casos de teste, e Mocha executará cada it() como um teste de unidade. Para organizar vários testes unitários, podemos describe() uma funcionalidade comum e, assim, estruturar os testes Mocha.

Provavelmente isso é melhor descrito com um exemplo concreto:

"use strict";
const expect = require('chai').expect;

describe('Math', function() {
    describe('#abs()', function() {
        it('should return positive value of given negative number', function() {
            expect(Math.abs(-5)).to.be.equal(5);
        });
    });
});

No describe() método, definimos um nome de teste, сhamado #abs(). Você também pode executar testes individualmente pelo nome deles – isso será abordado mais tarde.

Observação: Com os testes Mocha, você não precisa require() qualquer um dos métodos Mocha. Esses métodos são fornecidos globalmente quando executados com o mocha comando.

Para executar esses testes, salve seu arquivo e use o mocha comando:

$ mocha .

  Math
    #abs()
      ✓ should return positive value of given number 


  1 passing (9ms)

A saída é um detalhamento dos testes executados e seus resultados. Observe como o aninhado describe() as chamadas são transferidas para a saída de resultados. É útil ter todos os testes de um determinado método ou recurso aninhados.

Esses métodos são a base para a estrutura de testes Mocha. Use-os para redigir e organizar seus testes como desejar. Veremos um exemplo disso na próxima seção.

Escrevendo testes com Mocha e Chai

A maneira recomendada de organizar seus testes dentro do seu projeto é colocar todos eles em seus próprios /test diretório. Por padrão, o Mocha verifica testes de unidade usando os globs ./test/*.js e ./test/*.coffee. A partir daí, ele carregará e executará qualquer arquivo que chame o describe() método.

Confira nosso guia prático e prático para aprender Git, com práticas recomendadas, padrões aceitos pelo setor e folha de dicas incluída. Pare de pesquisar comandos Git no Google e realmente aprender -lo!

É comum colocar o sufixo nos arquivos de teste com .test.js para os arquivos de origem que contêm testes Mocha:

├── package.json
├── lib
│   ├── db.js
│   ├── models.js
│   └── util.js
└── test
    ├── db.test.js
    ├── models.test.js
    ├── util.test.js
    └── util.js

util.js no test diretório não conteria nenhum teste de unidade, apenas funções utilitárias para ajudar nos testes.

Note: Você pode usar qualquer estrutura que faça sentido para você, os testes unitários são selecionados automaticamente.

Quando se trata de realmente escrever os testes, ajuda organizá-los usando o describe() métodos. Você pode organizá-los por recurso, função, arquivo ou qualquer outro nível arbitrário. Por exemplo, um arquivo de teste organizado para descrever o funcionamento no nível da função se parece com:

"use strict";

const expect = require('chai').expect;

describe('Math', function() {
    describe('#abs()', function() {
        it('should return positive value of given negative number', function() {
            expect(Math.abs(-5)).to.be.equal(5);
        });

        it('should return positive value of given positive number', function() {
            expect(Math.abs(3)).to.be.equal(3);
        });

        it('should return 0 given 0', function() {
            expect(Math.abs(0)).to.be.equal(0);
        });
    });
});

A execução dos testes forneceria a saída:

$ mocha .

  Math
    #abs()
      ✓ should return positive value of given negative number 
      ✓ should return positive value of given positive number 
      ✓ should return 0 given 0 


  3 passing (11ms)

Expandindo ainda mais, você pode até ter testes para vários métodos em um único arquivo. Neste caso, os métodos são agrupados pelo Math objeto:

"use strict";

const expect = require('chai').expect;

describe('Math', function() {
    describe('#abs()', function() {
        it('should return positive value of given negative number', function() {
            expect(Math.abs(-5)).to.be.equal(5);
        });

        it('should return positive value of given positive number', function() {
            expect(Math.abs(3)).to.be.equal(3);
        });

        it('should return 0 given 0', function() {
            expect(Math.abs(0)).to.be.equal(0);
        });
    });

    describe('#sqrt()', function() {
        it('should return the square root of a given positive number', function() {
            expect(Math.sqrt(25)).to.be.equal(5);
        });

        it('should return NaN for a given negative number', function() {
            expect(Math.sqrt(-9)).to.be.NaN;
        });

        it('should return 0 given 0', function() {
            expect(Math.sqrt(0)).to.be.equal(0);
        });
    });
});

O que resulta em:

$ mocha .

  Math
    #abs()
      ✓ should return positive value of given negative number 
      ✓ should return positive value of given positive number 
      ✓ should return 0 given 0 
    #sqrt()
      ✓ should return the square root of a given positive number 
      ✓ should return NaN for a given negative number 
      ✓ should return 0 given 0 


  6 passing (10ms)

Ganchos Mocha – antes(), depois(), antesEach() e depoisEach()

É certo que a maioria dos testes unitários não são tão simples. Muitas vezes você provavelmente precisará de outros recursos para realizar seus testes, como um banco de dados ou algum outro recurso externo (ou um mock/stub deles). Para configurar isso, podemos usar um ou mais dos seguintes Gancho Mocha métodos:

  • before(): É executado antes de todos os testes no bloco fornecido
  • beforeEach(): É executado antes de cada teste no bloco fornecido
  • after(): É executado após todos os testes no bloco fornecido
  • afterEach(): É executado após cada teste no bloco fornecido

Esses ganchos são o local perfeito para realizar o trabalho de configuração e desmontagem necessário para seus testes. Um dos casos de uso comuns é estabelecer uma conexão com seu banco de dados antes de executar os testes:

"use strict";

const expect = require('chai').expect;
let User = require('../models').User;

describe('Users', function() {

    let database = null;

    before(function(done) {
        
    });

    afterEach(function(done) {
        
    });

    describe('#save()', function() {
        it('should save User data to database', function(done) {
            
        });
    });

    describe('#load()', function() {
        it('should load User data from database', function(done) {
            
        });
    });
});

Antes qualquer dos testes são executados, a função enviada ao nosso before() O método é executado (e executado apenas uma vez durante os testes), o que estabelece uma conexão com o banco de dados. Feito isso, nossos conjuntos de testes serão executados.

Como não queremos que os dados de um conjunto de testes afetem nossos outros testes, precisamos limpar os dados do nosso banco de dados após a execução de cada conjunto. Isso é o que afterEach() é para. Usamos este gancho para limpar todos os dados do banco de dados após cada O caso de teste é executado, para que possamos começar do zero para os próximos testes.

Executando testes Mocha

Na maioria dos casos, esta parte é bastante simples. Supondo que você já instalou o Mocha e navegou até o diretório do projeto, a maioria dos projetos só precisa usar o mocha comando sem argumentos para executar seus testes:

$ mocha


  Math
    #abs()
      ✓ should return positive value of given negative number 
      ✓ should return positive value of given positive number 
      ✓ should return 0 given 0 
    #sqrt()
      ✓ should return the square root of a given positive number 
      ✓ should return NaN for a given negative number 
      ✓ should return 0 given 0 


  6 passing (10ms)

Isso é um pouco diferente dos nossos exemplos anteriores, pois não precisamos informar ao Mocha onde nossos testes estavam localizados. Neste exemplo, o código de teste está no local esperado de /test.

Existem, no entanto, algumas opções úteis que você pode usar ao executar testes. Se alguns de seus testes falharem, por exemplo, você provavelmente não desejará executar o conjunto inteiro toda vez que fizer uma alteração. Para alguns projetos, o conjunto de testes completo pode levar alguns minutos para ser concluído. É muito tempo perdido se você realmente precisar executar apenas um teste.

Para casos como este, você deve diga ao Mocha quais testes executar. Isso pode ser feito usando o -g or -f opções.

Para executar testes individuais, você pode fornecer o -g flag e adicione um padrão comum entre os testes que você deseja executar. Por exemplo, se você quiser executar o #sqrt() testes:

$ mocha -g sqrt

  Math
    #sqrt()
      ✓ should return the square root of a given positive number 
      ✓ should return NaN for a given negative number 
      ✓ should return 0 given 0 


  3 passing (10ms)

Observe que o #abs() testes não foram incluídos nesta execução. Se você planejar de acordo com os nomes dos seus testes, esta opção poderá ser utilizada para executar apenas seções específicas dos seus testes.

Estas não são as únicas opções úteis, no entanto. Aqui estão mais algumas opções de Mocha que você pode querer conferir:

  • --invert: Inverte -g e -f fósforos
  • --recursive: Inclui subdiretórios
  • --harmony: Habilite todos os recursos de harmonia no Node

Note: Você pode conferir a lista completa de opções usando o mocha -h comando, ou em esta página.

Onde aprender mais? Há muito mais sobre este tópico do que podemos cobrir em um pequeno artigo, então se você quiser saber mais, recomendamos verificar o site oficial. ágata e Chai documentação.

Conclusão

Tenha em mente que tanto o Mocha quanto o Chai podem ser usados ​​para testar praticamente qualquer tipo de projeto Node, seja uma biblioteca, uma ferramenta de linha de comando ou até mesmo um site. Utilizando as várias opções e plug-ins disponíveis, você poderá satisfazer suas necessidades de teste com bastante facilidade. Cada uma dessas bibliotecas é muito útil para validar seu código e deve ser usada em praticamente todos os seus projetos Node.

Esperançosamente, isso serviu como uma introdução útil a Mocha e Chai. Há muito mais para aprender do que apresentei aqui, portanto, verifique a documentação para obter mais informações.

Carimbo de hora:

Mais de Abuso de pilha