Uma introdução ao teste de unidade de jasmim

Ahmed Bouchefra Blocked Desbloquear Seguir Seguindo 13 de dezembro de 2018

O Jasmine é a biblioteca JS mais popular para aplicativos da web de teste de unidade. Neste tutorial, projetado para iniciantes, apresentaremos um guia rápido e completo para testes com o Jasmine.

Você será apresentado ao Jasmine, uma popular estrutura de testes orientada por comportamento para JavaScript. Também veremos um exemplo prático simples de como escrever testes de unidade com o Jasmine, que pode ajudá-lo a verificar erros no seu código.

Em poucas palavras, veremos como escrever suítes de testes, especificações e expectativas e como aplicar adaptadores internos do Jasmine ou criar seus próprios combinadores personalizados

Também veremos como você pode agrupar suítes para organizar seus testes para bases de código mais complexas.

Apresentando o Jasmine

Jasmine é um framework de desenvolvimento orientado a comportamento JavaScript muito popular (em BDD, você escreve testes antes de escrever código real) para aplicativos JavaScript de teste de unidade. Ele fornece utilitários que podem ser usados para executar testes automatizados para código síncrono e assíncrono.

Jasmine tem muitos recursos, como:

  • É rápido e tem pouca sobrecarga e nenhuma dependência externa.
  • É uma biblioteca de baterias incluídas e oferece tudo o que você precisa para testar seu código.
  • Está disponível tanto para o Node como para o navegador.
  • Pode ser usado com outras linguagens como Python e Ruby.
  • Não requer o DOM.
  • Ele fornece uma sintaxe limpa e fácil de entender e também uma API rica e direta.
  • Podemos usar linguagem natural para descrever os testes e os resultados esperados.

Jasmine é uma ferramenta de código aberto que está disponível sob a licença MIT permissiva. No momento em que este artigo foi escrito, a versão principal mais recente é o Jasmine 3.0, que fornece novos recursos e algumas alterações significativas. A versão 2.99 do Jasmine fornecerá avisos de desaprovação diferentes para suítes com comportamento diferente na versão 3.0, o que facilitará a migração dos desenvolvedores para a nova versão.

Você pode ler sobre os novos recursos e quebrar as alterações deste documento .

Usando Jasmim

Você pode usar o Jasmine de várias maneiras diferentes:

  • da maneira antiga, incluindo o núcleo do Jasmine e seus arquivos de teste usando uma tag <script> ,
  • como uma ferramenta CLI usando o Node.js,
  • como uma biblioteca no Node.js,
  • como parte de um sistema de compilação como Gulp.js ou Grunt.js via grunt-contrib-jasmine e gulp-jasmine-browser

Você também pode usar o Jasmine para testar seu código Python com jasmine-py, que pode ser instalado a partir do PyPI usando o comando pip install jasmine . Este pacote contém um servidor Web que serve e executa uma suíte Jasmine para seu projeto e um script CLI para executar testes e integrações contínuas.

Jasmine também está disponível para projetos Ruby via jasmine-gem, que pode ser instalado adicionando gem 'jasmine' ao seu Gemfile e executando a bundle install . Inclui um servidor para servir e executar testes, um script CLI e também geradores para projetos Ruby on Rails.

Agora vamos nos concentrar em como usar o Jasmine com JavaScript:

Usando o Jasmine Independente

Comece baixando a última versão do Jasmine da página de lançamentos .

Em seguida, basta extrair o arquivo zip, de preferência dentro de uma pasta no projeto que você deseja testar.

A pasta conterá vários arquivos e pastas padrão:

/src : contém os arquivos de origem que você deseja testar. Isso pode ser excluído se você já tiver a configuração de pasta do seu projeto ou também pode ser usado quando apropriado para hospedar seu código-fonte.

/lib : contém os arquivos principais do Jasmine.

/spec : contém os testes que você vai escrever.

SpecRunner.html : este arquivo é usado como um SpecRunner.html testes. Você executa suas especificações simplesmente iniciando este arquivo.

Este é o conteúdo de um arquivo SpecRunner.html padrão:

 <!DOCTYPE html> 
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner v3.2.1</title>

<link rel="shortcut icon" type="image/png" href="lib/jasmine-3.2.1/jasmine_favicon.png">
<link rel="stylesheet" href="lib/jasmine-3.2.1/jasmine.css">

<script src="lib/jasmine-3.2.1/jasmine.js"></script>
<script src="lib/jasmine-3.2.1/jasmine-html.js"></script>
<script src="lib/jasmine-3.2.1/boot.js"></script>

<!-- include source files here... -->
<script src="src/Player.js"></script>
<script src="src/Song.js"></script>

<!-- include spec files here... -->
<script src="spec/SpecHelper.js"></script>
<script src="spec/PlayerSpec.js"></script>

</head>
<body>
</body>
</html>

Lembre-se de que você precisa alterar os arquivos incluídos nas pastas /src e /spec para conter sua fonte real e seus arquivos de teste.

Usando o Jasmine como uma biblioteca

Você também pode usar o Jasmine como uma biblioteca em seu projeto. Por exemplo, o código a seguir importa e executa o Jasmine:

 
var Jasmine = require('jasmine');
var jasmine = new Jasmine();

jasmine.loadConfigFile('spec/support/jasmine.json');

jasmine.execute();

Primeiro nós precisamos / importamos o Jasmine e usamos o método loadConfigFile() para carregar o arquivo de configuração disponível no caminho spec/support/jasmine.json e finalmente executamos o Jasmine.

Usando o Jasmine via o CLI

Você também pode usar o Jasmine a partir do CLI, que permite executar facilmente os testes do Jasmine e, por padrão, exibir os resultados no terminal.

Seguiremos essa abordagem para executar nossos testes de exemplo neste guia, portanto, primeiro siga em frente e execute o seguinte comando para instalar o Jasmine globalmente:

 npm install -g jasmine 

Você pode precisar executar o sudo para instalar pacotes npm globalmente, dependendo da configuração do npm .

Agora, crie uma pasta para o seu projeto e navegue dentro dela:

 $ mkdir jasmine-project 
$ cd jasmine-project

Em seguida, execute o seguinte comando para inicializar seu projeto para o Jasmine:

Esse comando simplesmente cria uma pasta de especificações e um arquivo de configuração JSON. Esta é a saída do comando dir :

 . 
??? spec
??? support
??? jasmine.json

2 directories, 1 file

Este é o conteúdo de um arquivo jasmine.json padrão:

 { 
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js"
],
"stopSpecOnExpectationFailure": false,
"random": true
}
  • spec_dir : especifica onde o Jasmine procura por arquivos de teste.
  • spec_files : especifica os padrões dos arquivos de teste, por padrão, todos os arquivos JS que terminam com strings Spec ou spec .
  • helpers : especifica onde o Jasmine procura por arquivos auxiliares. Os arquivos auxiliares são executados antes das especificações e podem ser usados para definir os correspondentes personalizados.
  • stopSpecOnExpectationFailure : quando definido como true, interromperá imediatamente uma especificação na primeira falha de uma expectativa (pode ser usada como uma opção da CLI por meio de --stop-on-failure ).
  • random : quando definido como true O Jasmine executará pseudo-aleatoriamente os casos de teste (pode ser usado como uma opção CLI via --random ).

Os arrays spec_files e helpers também podem conter padrões Glob (graças ao pacote node-glob ) para especificar caminhos de arquivo que são padrões que você normalmente usa para especificar um conjunto de arquivos ao trabalhar no Bash (por exemplo, ls *.js ).

Se você não usar o local padrão para o arquivo de configuração jasmine.json , basta especificar o local personalizado por meio da opção jasmine --config .

Você pode encontrar mais opções de CLI nos documentos oficiais.

Noções básicas sobre jasmim

Nesta seção, aprenderemos sobre os elementos básicos do teste Jasmine, como suítes, especificações, expectativas, correspondências e espiões, etc.

Na pasta do seu projeto, execute o seguinte comando para inicializar um novo módulo do Nó:

Isso criará um arquivo package.json com informações padrão:

 { 
"name": "jasmine-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

Em seguida, crie um arquivo index.js e adicione o seguinte código:

 function fibonacci(n){ 

if (n === 1) {
return [0, 1];
}
else {
var s = fibonacci(n - 1);
s.push(s[s.length - 1] + s[s.length - 2]);
return s;
}
}
function isPrime(num){
for (let i = 2; i < num; i++)
if (num % i === 0) return false;
return num !== 1 && num !== 0;
}
function isEven(n) {
return n % 2 == 0;
}
function isOdd(n) {
return Math.abs(n % 2) == 1;
}

function toLowerCase(str){
return str.toLowerCase();
}
function toUpperCase(str){
return str.toUpperCase();
}
function contains(str, substring, fromIndex){
return str.indexOf(substring, fromIndex) !== -1;
}
function repeat(str, n){
return (new Array(n + 1)).join(str);
}

module.exports = {
fibonacci: fibonacci,
isPrime: isPrime,
isEven: isEven,
isOdd: isOdd,
toLowerCase: toLowerCase,
toUpperCase: toUpperCase,
contains: contains,
repeat: repeat
};

Suítes

Uma suíte agrupa um conjunto de especificações ou casos de teste. Ele é usado para testar um comportamento específico do código JavaScript que geralmente é encapsulado por um objeto / classe ou uma função. Ele é criado usando a função global Jasmine describe() que usa dois parâmetros, o título do conjunto de testes e uma função que implementa o código real do conjunto de testes.

Vamos começar criando nossa primeira suíte de testes. Dentro da pasta spec crie um arquivo MyJSUtilitiesSpec.js e adicione:

 describe("MyJSUtilities", function() { /* ... */ }); 

MyJSUtilities é o nome deste conjunto de testes de nível superior.

Como agrupar e aninhar suítes

Para melhor organização e descrição precisa do nosso conjunto de testes, podemos aninhar suítes dentro da suíte de nível superior. Por exemplo, vamos adicionar dois conjuntos ao pacote MyJSUtilities :

 describe("String Utils", function() { 
/*...*/
});

describe("Math Utils", function() {
/*...*/
});

Dentro da suíte Math Utils , vamos também adicionar dois conjuntos aninhados:

 describe("Basic Math Utils", function() { 
/* ... */
});
describe("Advanced Math Utils", function() {
/* ... */
});

Estamos agrupando testes relacionados em testes para Utilitários de Cadeia , Utilitários Básicos de Matemática e Utilitários Avançados de Matemática e aninhando-os dentro do conjunto de testes de nível superior MyJSUtilities . Isso irá compor suas especificações como árvores semelhantes a uma estrutura de pastas.

A estrutura de aninhamento será mostrada no relatório, o que facilita a localização de testes com falha.

Como excluir suítes

Você pode desativar temporariamente um conjunto usando a função xdescribe() . Ele tem a mesma assinatura (parâmetros) que a função describe() , o que significa que você pode rapidamente desabilitar seus pacotes existentes simplesmente adicionando um x à função.

As especificações em uma função xdescribe() serão marcadas como pendentes e não serão executadas no relatório.

Especificações

Uma especificação declara um caso de teste que pertence a um conjunto de testes. Isso é feito chamando a função global Jasmine it() que recebe dois parâmetros, o título da especificação (que descreve a lógica que queremos testar) e uma função que implementa o caso de teste real.

Uma especificação pode conter uma ou mais expectativas. Cada expectativa é simplesmente uma afirmação que pode retornar true ou false . Para que a especificação seja passada, todas as expectativas pertencentes à especificação devem ser true caso contrário, a especificação falhará.

Dentro da nossa suíte String Utils , adicione estas especificações:

 describe("String Utils", function() { 
it("should be able to lower case a string",function() {
/*...*/
});
it("should be able to upper case a string",function() {
/*...*/
});
it("should be able to confirm if a string contains a substring",function() {
/*...*/
});
it("should be able repeat a string multiple times",function() {
/*...*/
});

});

Dentro da nossa suíte Basic Math Utils , vamos adicionar algumas especificações:

 describe("Basic Math Utils", function() { 
it("should be able to tell if a number is even",function() {
/*...*/
});
it("should be able to tell if a number is odd",function() {
/*...*/
});
});

Para os Advanced Math Utils , vamos adicionar as especificações:

 describe("Advanced Math Utils", function() { 
it("should be able to tell if a number is prime",function() {
/*...*/
});
it("should be able to calculate the fibonacci of a number",function() {
/*...*/
});
});

Como excluir especificações

Assim como suites, você também pode excluir especificações individuais usando a função xit() , que desabilita temporariamente a especificação it() e marca a especificação como pendente.

Expectativas

As expectativas são criadas usando a função expect() que recebe um valor chamado real (isto pode ser valores, expressões, variáveis, funções ou objetos etc.). As expectativas compõem a especificação e são usadas junto com as funções de correspondência (via encadeamento) para definir o que o desenvolvedor espera de uma unidade de código específica para executar.

Uma função matcher compara entre um valor real (passado para a função expect() com um encadeamento) e um valor esperado (transmitido diretamente como um parâmetro para o correspondente) e retorna true ou false, que passa ou falha na especificação.

Você pode encadear a função expect() com vários correspondentes. Para negar / inverter o resultado booleano de qualquer matcher, você pode usar a palavra not keyword antes de chamar o matcher.

Vamos implementar as especificações do nosso exemplo. Por enquanto, usaremos o expect() com o matcher nothing() que faz parte dos matchers internos que veremos mais tarde. Isso vai passar todas as especificações, já que não estamos esperando nada neste momento.

 describe("MyJSUtilities", function() { 

describe(">String Utils", function() {
it("should be able to lower case a string",function() {
expect().nothing();
});
it("should be able to upper case a string",function() {
expect().nothing();
});
it("should be able to confirm if a string contains a substring",function() {
expect().nothing();
});
it("should be able repeat a string multiple times",function() {
expect().nothing();
});

});

describe("Math Utils", function() {
describe("Basic Math Utils", function() {
it("should be able to tell if a number is even",function() {
expect().nothing();
});
it("should be able to tell if a number is odd",function() {
expect().nothing();
});

});
describe("Advanced Math Utils", function() {
it("should be able to tell if a number is prime",function() {
expect().nothing();
});
it("should be able to calculate the fibonacci of a number",function() {
expect().nothing();
});
});
});

});

Esta é uma captura de tela dos resultados neste momento:

Temos oito especificações aprovadas e zero falhas.

Você pode usar adaptadores internos ou também criar seus próprios adaptadores personalizados para suas necessidades específicas.

Marcadores Integrados

Jasmine fornece um rico conjunto de matchers embutidos. Vamos ver alguns dos mais importantes:

  • toBe() para teste de identidade,
  • toBeNull() para testes de null ,
  • toBeUndefined()/toBeDefined() para testes para undefined / não undefined ,
  • toBeNaN() para testes de NaN (não um número)
  • toEqual() para testes de igualdade,
  • toBeFalsy()/toBeTruthy() para testes de falsidade / veracidade etc.

Você pode encontrar a lista completa de correspondentes dos documentos .

Vamos agora implementar nossas especificações com alguns desses matchers quando apropriado. Primeiro importe as funções que estamos testando em nosso arquivo MyJSUtilitiesSpec.js :

 const utils = require("../index.js"); 

Em seguida, comece com a suíte String Utils e altere expect().nothing() com as expectativas apropriadas.

Por exemplo, para a primeira especificação, esperamos que o método toLowerCase() seja definido pela primeira vez e, em segundo lugar, para retornar uma string de letra minúscula, isto é:

 it("should be able to lower case a string",function() { 
expect(utils.toLowerCase).toBeDefined();
expect(utils.toLowerCase("HELLO WORLD")).toEqual("hello world");
});

Este é o código completo para o pacote:

 describe(">String Utils", function() { 
it("should be able to lower case a string",function() {
expect(utils.toLowerCase).toBeDefined();
expect(utils.toLowerCase("HELLO WORLD")).toEqual("hello world");

});

it("should be able to upper case a string",function() {
expect(utils.toUpperCase).toBeDefined();
expect(utils.toUpperCase("hello world")).toEqual("HELLO WORLD");
});

it("should be able to confirm if a string contains a substring",function() {
expect(utils.contains).toBeDefined();
expect(utils.contains("hello world","hello",0)).toBeTruthy();

});

it("should be able repeat a string multiple times",function() {
expect(utils.repeat).toBeDefined();
expect(utils.repeat("hello", 3)).toEqual("hellohellohello");
});

});

Matchers personalizados

O Jasmine fornece a capacidade de escrever adaptadores personalizados para implementar asserções não cobertas pelos correspondentes internos ou apenas para tornar os testes mais descritivos e legíveis.

Por exemplo, vamos pegar a seguinte especificação:

 it("should be able to tell if a number is even",function() { 
expect(utils.isEven).toBeDefined();
expect(utils.isEven(2)).toBeTruthy();
expect(utils.isEven(1)).toBeFalsy();
});

Vamos supor que o método isEven() não esteja implementado. Se executarmos os testes, receberemos mensagens como a seguinte captura de tela:

A mensagem de falha que recebemos diz " Definido indefinido" deve ser definido, o que não nos dá nenhuma pista do que está acontecendo. Então, vamos tornar essa mensagem mais significativa no contexto do nosso domínio de código (isso será mais útil para bases de código complexas). Para este assunto, vamos criar um comparador personalizado.

Criamos correspondentes personalizados usando o método addMatchers() , que recebe um objeto composto de uma ou várias propriedades que serão adicionadas como correspondentes. Cada propriedade deve fornecer uma função de fábrica que utiliza dois parâmetros: util , que possui um conjunto de funções de utilitário para os correspondentes (see: matchersUtil.js ) e customEqualityTesters que precisam ser passados se util.equals for chamado e deve retornar um objeto com uma função de compare que será chamada para verificar a expectativa.

Precisamos registrar o comparador personalizado antes de executar cada especificação usando o método beforeEach() :

 describe("/Basic Math Utils", function () { 
beforeEach(function () {
jasmine.addMatchers({
hasEvenMethod: function (util, customEqualityTesters) {
return {
compare: function (actual, expected) {
var result = { pass: utils.isEven !== undefined };
if (result.pass) {
result.message = "Expected isEven() to be not defined."
}
else {
result.message = "Expected isEven() to be defined."
}
return result;
}
}
}
});
});
/*...*/
});

Podemos, então, usar o correspondente personalizado em vez de expect(utils.isEven).toBeDefined() :

 expect().hasEvenMethod(); 

Isso nos dará uma mensagem de falha melhor:

Usando beforeEach () e afterEach ()

Para inicializar e limpar suas especificações, o Jasmine fornece duas funções globais, beforeEach() e afterEach() :

  • A função beforeEach é chamada uma vez antes de cada especificação no conjunto em que é chamada.
  • A função afterEach é chamada uma vez após cada especificação no pacote em que é chamada.

Por exemplo, se você precisar usar quaisquer variáveis em sua suíte de testes, você pode simplesmente declará-las no início da função describe() e colocar qualquer código de inicialização ou instanciação dentro de uma função beforeEach() . Finalmente, você pode usar a função afterEach() para redefinir as variáveis após cada especificação, para que você possa ter um teste de unidade puro sem a necessidade de repetir o código de inicialização e limpeza para cada especificação.

A função beforeEach() também é perfeitamente combinada com muitas APIs do Jasmine, como o método addMatchers() , para criar correspondências personalizadas ou também com a função done() para aguardar operações assíncronas antes de continuar o teste.

Falha em um teste

Você pode forçar um teste a falhar usando o método global fail() disponível no Jasmine. Por exemplo:

 it("should explicitly fail", function () { fail('Forced to fail'); }); 

Você deve receber o seguinte erro:

Teste para exceções

Quando você está testando seu código, erros e exceções podem ser lançados, portanto, talvez seja necessário testar esses cenários. O Jasmine fornece os toThrow() e toThrowError() para testar quando uma exceção é lançada ou para testar uma exceção específica, respectivamente.

Por exemplo, se tivermos uma função que lança uma exceção TypeError :

 function throwsError() { throw new TypeError("A type error"); } 

Você poderia escrever uma especificação para testar se uma exceção for lançada:

 it('it should throw an exception', function () { expect(throwsError).toThrow(); }); 

Ou você também pode usar o teste para a exceção TypeError específica:

 it('it should throw a TypeError', function () { expect(throwsError).toThrowError(TypeError); }); 

Entendendo espiões

Mais frequentemente, métodos dependem de outros métodos. Isto significa que quando você está testando um método, você também pode acabar testando suas dependências. Isso não é recomendado em testes, ou seja, você precisa testar a função pura isolando o método e vendo como ele se comporta com um conjunto de entradas.

O Jasmine fornece espiões que podem ser usados para espionar / escutar chamadas de método em objetos e relatar se um método é chamado e com qual contexto e argumentos.

O Jasmine fornece duas maneiras de espionar as chamadas de método: usando os spyOn() ou createSpy() .

Você pode usar spyOn() quando o método já existir no objeto, caso contrário você precisará usar jasmine.createSpy() que retorna uma nova função.

Por padrão, um espião só relatará se uma chamada foi feita sem chamar através da função spied (ou seja, a função irá parar de executar), mas você pode alterar o comportamento padrão usando estes métodos:

  • and.callThrough() : chame a função original,
  • and.returnValue(value) : retorna o valor especificado,
  • and.callFake(fn) : chama a função falsa ao invés da original,
  • and.throwError(err) : lançar um erro,
  • and.stub() : redefine o comportamento padrão de stub.

Você pode usar um espião para coletar estatísticas de tempo de execução sobre a função espiada, por exemplo, se você quiser saber quantas vezes sua função foi chamada.

Digamos que queremos ter certeza de que nosso método toUpperCase() está usando o método String.toUpperCase() , precisamos simplesmente espionar String.toUpperCase() usando:

 it("should be able to upper case a string", function () { 
 var spytoUpperCase = spyOn(String.prototype, 'toUpperCase') 
 expect(utils.toUpperCase).toBeDefined(); expect(utils.toUpperCase("hello world")).toEqual("HELLO WORLD"); expect(String.prototype.toUpperCase).toHaveBeenCalled(); expect(spytoUpperCase.calls.count()).toEqual(1); 
});

O teste falhou devido à segunda expectativa porque utils.toUpperCase("hello world") retornou indefinido em vez do esperado HELLO WORLD. Isso porque, como mencionamos, antes de criar o spy em toUpperCase() , o método não é executado. Precisamos mudar esse comportamento padrão chamando callThrough() :

Por favor, note que uma função de spy substitui a função spied por um stub por padrão. Se você precisar chamar a função original, você pode adicionar .and.callThrough() ao seu objeto spy .

 var spytoUpperCase = spyOn(String.prototype, 'toUpperCase').and.callThrough(); 

Agora todas as expectativas passam.

Você também pode usar and.callFake() ou and.returnValue() para falsificar a função espiada ou apenas o valor de retorno se você não chamar a função real:

 var spytoUpperCase = spyOn(String.prototype, 'toUpperCase').and.returnValue("HELLO WORLD"); 
 var spytoUpperCase = spyOn(String.prototype, 'toUpperCase').and.callFake(function(){ return "HELLO WORLD"; }); 

Agora, se não usarmos o construído em String.toUpperCase() em nossa própria implementação de utils.toUpperCase() , obteremos essas falhas:

As duas expectativas expect(String.prototype.toUpperCase).toHaveBeenCalled() expect(spytoUpperCase.calls.count()).toEqual(1) falharam.

Como lidar com assincronismo no jasmim

Se o código que você está testando contiver operações assíncronas, você precisará informar a Jasmine quando as operações assíncronas forem concluídas.

Por padrão, o Jasmine aguarda a conclusão de qualquer operação assíncrona, definida por um retorno de chamada, promessa ou a palavra async chave async . Se o Jasmine encontrar um retorno de chamada, prometer ou assíncrono em uma dessas funções: beforeEach , afterEach , beforeAll , afterAll , e it aguardará que o assíncrono seja feito antes de prosseguir para a próxima operação.

Usando done() com beforeEach() / it() ..

https://volaresystems.com/blog/post/2014/12/09/Testing-async-calls-with-Jasmine

Vamos pegar nosso exemplo simulateAsyncOp() que simula uma operação assíncrona usando setTimeout() . Em um cenário do mundo real, isso pode ser um pedido Ajax ou qualquer coisa similar que acontece de forma assíncrona:

 function simulateAsyncOp(callback){ 
 setTimeout(function () { callback(); }, 2000); 
}

Para testar essa função, podemos usar a função beforeEach() com o callback especial done() . Nosso código precisa invocar done() para informar ao Jasmine que a operação assíncrona foi concluída:

 describe("/Async Op", function () { 

var asyncOpCompleted = false;

beforeEach(function (done) {
utils.simulateAsyncOp(function(){
asyncOpCompleted = true;
done();
});
});
it("should be able to tell if the async call has completed", function () {
expect(asyncOpCompleted).toEqual(true);
});
});

Podemos notar rapidamente uma desvantagem desse método, portanto, precisamos escrever nosso código para aceitar o retorno de chamada done() . Em nosso caso, não codificamos o método done() em nosso simulateAsyncOp(fn) mas fornecemos um parâmetro de retorno apenas para poder chamar done() .

Usando promessas

Se você não quiser criar código que depende de como você escreve seu teste, você pode usar uma promessa e chamar o retorno de chamada done() quando a promessa for resolvida. Ou melhor ainda, no Jasmine 2.7+, se o seu código retornar um Promise , o Jasmine aguardará até ser resolvido ou rejeitado antes de executar o próximo código.

Usando async / await

O Jasmine 2.7+ suporta async e await chamadas nas especificações. Isto alivia-lo de colocar afirma em um .then() ou .catch() bloco.

 it("should work with async/await", async () => { 
let completed = false;
completed = await utils.simulateAsyncOp(); expect(completed).toEqual(true);
});

Esta é a implementação do simulateAsyncOp :

 function simulateAsyncOp() { 
 return new Promise(resolve => { setTimeout(() => { resolve(true); }, 1000); }); } 

Usando o Jasmine Clock

O relógio Jasmine é usado para testar código assíncrono que depende de funções de tempo, como setTimeout() , da mesma forma que testamos código síncrono, zombando de APIs baseadas em tempo com métodos personalizados. Desta forma, você pode executar as funções testadas de forma síncrona, controlando ou avançando manualmente o relógio.

Você pode instalar o relógio Jasmine chamando a função jasmine.clock().install em sua especificação ou suite.

Depois de usar o relógio, você precisa desinstalá-lo para restaurar as funções originais.

Com o relógio Jasmine, você pode controlar as funções setTimeout ou setInterval do JavaScript marcando o relógio para avançar no tempo usando a função jasmine.clock().tick , que leva o número de milissegundos com os quais você pode se mover.

Você também pode usar o Relógio Jasmine para zombar da data atual.

 beforeEach(function () { 
jasmine.clock().install();
});

afterEach(function() {
jasmine.clock().uninstall();
});

it("should call the asynchronous operation synchronously", function() {
var completed = false;
utils.simulateAsyncOp(function(){
completed = true;
});
expect(completed).toEqual(false);
jasmine.clock().tick(1001);
expect(completed).toEqual(true);
});

Esta é a função simulateAsyncOp :

 function simulateAsyncOp(callback){ 
 setTimeout(function () { callback(); }, 1000); 
}

Caso você não tenha especificado um horário para a função mockDate , ela usará a data atual.

Manipulando Erros

Se o seu código assíncrono falhar devido a algum erro, você deseja que suas especificações falhem corretamente. Começando com o Jasmine 2.6+, quaisquer erros não tratados são enviados para a especificação atualmente executada.

O Jasmine também fornece uma maneira que você pode usar se precisar explicitamente falhar suas especificações:

  • usando o retorno de chamada done() com beforeEach() chamando o done.fail(err) ,
  • simplesmente passando um erro para o callback done(err) (Jasmine 3+),
  • chamando o método reject() de uma Promise .

Conclusão

Neste guia, apresentamos o Jasmine e vimos como começar a usar o Jasmine na unidade para testar seu código JavaScript. Obrigado pela leitura!