Teste seu Javascript

Teste seu Javascript

Por que devo testar meu código Javascript?

As aplicações web estão cada vez mais dinâmicas. Fazendo com que trabalhamos mais no client-side, deixando nosso código Javascript com bastante responsabilidade.

Hoje é comum termos aplicações utilizando Frameworks MVC no front-end, já imaginou aquela app gigante em React sem nenhum teste cobrindo suas funcionalidades?

Como dar manutenção e garantir que o fluxo não foi quebrado em nenhum momento? Sem testes é difícil ter certeza de que as coisas estão consistentes.

TDD e BDD

São metodologias de desenvolvimento ágil.

No TDD (Test Driven Development) o desenvolvimento deve ser guiado por testes, onde um teste unitário deve ser escrito antes de uma funcionalidade do sistema. O objetivo, então, é fazer com que o teste passe com sucesso, significando que assim a funcionalidade está pronta e conta com garantia de qualidade.

No BDD (Behaviour Driven Development) o desenvolvimento deve ser guiado por comportamentos do sistema. Desta forma, um comportamento é priorizado em relação ao teste unitário, o que não exclui a execução do fluxo do TDD nesse processo.

Suite de testes

Vamos criar uma suite de testes simples onde vamos testar código Front-end.

Sabemos que pode-se utilizar Javascript no server, mas a maior dificuldade que temos é testar o código que depende do DOM, como um componente que tenha funcionalidades disparadas por hover, click, keypress, focus ou qualquer outra ação do usuário.

Então nesse post vamos focar apenas no client-side.

Para escrever nossos testes precisamos utilizar uma biblioteca, dentre as disponíveis a que eu tenho preferência é o Jasmine, mas existem outras bastante utilizadas como Mocha, QUnit e Jest.

Prefiro o Jasmine por vir com mais funcionalidades inclusas, se você for usar Mocha vai acabar precisando adicionar um expect.js e Sinon.js (ou semelhantes) na sua suite. Portanto vamos manter as coisas simples.

No fim a lib que você escolher não vai influenciar tanto, o que importa é ter nossos scripts testados.


Vamos usar Gulp e PhanthomJS para executar nossos testes, assim automatizamos o processo facilmente. Para isso crie o package.json do seu projeto:

mkdir meu-projeto && cd meu-projeto
npm init && npm install --save-dev gulp gulp-jasmine-phantom

Em seguida crie o arquivo gulpfile.js.

var gulp = require('gulp');
var jasmine = require('gulp-jasmine-phantom');

gulp.task('spec', function() {
  gulp
    .src(['./src/**/*.js', './spec/**/*.spec.js'])
    .pipe(jasmine({
      integration: true,
      jasmineVersion: '2.3'
    }));
});

Seguindo essa estrutura criamos nossos scripts na pasta /src e os arquivos de test em /spec, esses paths são configuráveis então posteriormente você pode adequar as condições do seu projeto.

Por convenção todo arquivo de test segue a nomeclatura arquivo.spec.js.

Fluxo de desenvolvimento

Vamos desenvolver um método chamado Sum que tem como objetivo somar 2 números. Então criamos o test para ele:

// ./spec/sum.spec.js'

describe('Sum', function() {
  it('Should return the sum of 2 numbers', function() {
    expect(Sum(2, 3)).toEqual(5);
  });
});

Para rodar os tests execute gulp spec no seu terminal. Naturalmente esse test vai falhar, pois ainda não implementamos nenhum método Sum.

Failed test

O nosso test falha, alertando sobre um ReferenceError dizendo que não conseguiu encontrar a variável/function Sum, então vamos criá-la agora.

Por hora não vamos implementar a funcionalidade, apenas retornar um valor fixo de 100.

// ./src/sum.js'

var Sum = function(x, y) {
  return 100; 
}

Ao rodar o test ele vai falhar novamente, mas agora ele avisa claramente que falhou porque o test esperava que o retorno da soma fosse 5 e não 100.

Failed test

Agora, refatoramos nosso script para funcionar corretamente como planejamos.

// ./src/sum.js'

var Sum = function(x, y) {
  return x + y; 
}

E ao rodar o test, ele passa com sucesso:

Succeed test

Com o test passando, temos certeza que nosso método faz o que devia e se posteriormente alguém alterar esse componente e quebrar a funcionalidade o teste voltará a falhar.

Testando seus componentes

Indo para um caso mais realístico, vamos supor que teremos que desenvolver um componente de estado de loading para os botões de um formulário.

Os requisitos são simples, quando clicamos nesse botão precisamos:

  • Adicionar a classe is-loading
  • Adicionar a propriedade disabled
  • Alterar seu texto para Loading...

Como no exemplo abaixo:

Loading Button

Criando os specs para garantir esses requisitos:

// ./spec/loading-button.spec.js'

describe('Loading Button', function() {
  var $button = $('<button>Submit</button>');

  beforeAll(function() {
    new LoadingButton($button);
    $button.trigger('click');
  });

  describe('When click', function() {
    it('Add "is-loading" class', function() {
      expect($button.hasClass('is-loading')).toBeTruthy();
    });

    it('Add disabled property', function() {
      expect($button.prop('disabled')).toBe(true);
    });

    it('Replace text to "Loading..."', function() {
      expect($button.text()).toMatch('Loading...');
    });
  });
});

Primeiro de tudo, nós criamos o DOM de um button fake, esse botão que receberá a ação de click, ele imita os botões reais da nossa aplicação para fins de teste. Chamamos isso de Mock.

O beforeAll executa antes dos testes, então nele nós instanciamos o componente e com jQuery simulamos um click usando o trigger.

Criamos expectations para cada requisito. Feito isso podemos criar de fato o nosso componente:

// ./src/loading-button.js'

function LoadingButton($button) {
  $button.on('click', function() {
    $(this)
      .addClass('is-loading')
      .prop('disabled', true)
      .text('Loading...');
  });
}

Rodando os testes:

Loading Button Tests

Sucesso! Agora temos nosso componente 100% coberto por testes, nos dando mais segurança para futuras manutenções.

Os próximos passos seriam usar uma ferramenta de CI (Continuous Integration) para rodar os tests automaticamente no repositório do seu projeto. Imagine que no seu time alguém alterou um código importante, aquele código que quebra toda sua aplicação e ninguém notou esse erro.

Se o seu repositório executa os tests automaticamente toda vez que for feito um commit no branch principal o CI vai rodar os tests sozinho e nos avisará se algo falhou. Para você que trabalha em um time ágil com bastante developers isso é uma prática muito boa para evitar que bugs vão para produção.

Algumas dessas ferramentas são: Travis CI, Circle CI e Semaphore CI.