O Jasmine é uma das ferramentas de teste para Javascript mais usadas atualmente. A sintaxe estilo BDD lembra bastante o RSpec, o que facilita a vida de quem já tem experiência com este.
Uma das dificuldades ao realizar testes de Javascript é como simular a passagem do tempo. Existem duas situações básicas onde isso acontece:
- Quando o código executa alguma animação, como fade in e slide down, por exemplo
- Quando definimos uma função que só será executada após um período de tempo determinado
O primeiro caso pode ser ilustrado com este exemplo básico:
<button id="button">Show Menu</button>
<div id="menu" style="display: none">Menu</div>
<script>
function example() {
$("#button").click(function() {
$("#menu").fadeIn();
});
}
example();
</script>
Um clique no botão faz com que o menu apareça usando a função jQuery.fadeIn. O teste para este código, a princípio, poderia ser algo assim:
describe("example test", function() {
beforeEach(function() {
example();
});
it("shows the menu after clicking the button", function() {
$("#button").click();
expect($("#menu")).toBeVisible();
});
});
O problema é que, como a animação do fade in leva um pequeno período de tempo para executar (400 ms por padrão), o menu ainda não está visível no momento em que a expectativa é executada. Uma solução inocente, mas pouco eficiente, para este problema seria executar a expectativa num setTimeout.
Neste caso específico, como a animação é feita usando jQuery, há uma propriedade jQuery.fx.off que permite desabilitar todas as animações. Desta forma, todas as transições são feitas instantaneamente, fazendo com que o teste original funcione:
describe("example test", function() {
var jQueryFxOff;
beforeEach(function() {
jQueryFxOff = $.fx.off;
$.fx.off = true;
example();
});
afterEach(function() {
$.fx.off = jQueryFxOff;
});
it("shows the menu after clicking the button", function() {
$("#button").click();
expect($("#menu")).toBeVisible();
});
});
Note que o valor original da propriedade é armazenado numa variável e restaurado após o teste, para evitarmos que esta configuração afete outros testes que serão executados em sequencia.
O segundo caso é quando temos algum código que só é executado após um período de tempo - usando setTimeout, por exemplo:
module = {
someRandomCode: function() {
},
waitForIt: function() {
setTimeout(this.someRandomCode, 5000);
}
}
module.waitForIt();
A melhor forma de testar este código é “fakeando” a passagem do tempo, para que o teste não precise aguardar. Uma boa ferramenta para isto são os os fake timers do Sinon.JS:
describe("my random test", function() {
var clock;
beforeEach(function() {
clock = sinon.useFakeTimers();
spyOn(module, "someRandomCode");
module.waitForIt();
});
afterEach(function() {
clock.restore();
});
it("tests my random code", function() {
clock.tick(5000);
expect(module.someRandomCode).toHaveBeenCalled();
});
});
Outra boa opção é usar o Jasmine Clock:
describe("my random test", function() {
beforeEach(function() {
jasmine.clock().install();
spyOn(module, "someRandomCode");
module.waitForIt();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it("tests my random code", function() {
jasmine.clock().tick(5000);
expect(module.someRandomCode).toHaveBeenCalled();
});
});