Como verifico eventos jQuery AJAX com Jasmine?

114

Estou tentando usar Jasmine para escrever algumas especificações de BDD para solicitações básicas de jQuery AJAX. No momento, estou usando Jasmine no modo autônomo (ou seja, através SpecRunner.html). Eu configurei SpecRunner para carregar jquery e outros arquivos .js. Alguma ideia de por que o seguinte não funciona? has_returned não se torna verdade, mesmo que o "yuppi!" alerta aparece bem.

describe("A jQuery ajax request should be able to fetch...", function() {

  it("an XML file from the filesystem", function() {
    $.ajax_get_xml_request = { has_returned : false };  
    // initiating the AJAX request
    $.ajax({ type: "GET", url: "addressbook_files/addressbookxml.xml", dataType: "xml",
             success: function(xml) { alert("yuppi!"); $.ajax_get_xml_request.has_returned = true; } }); 
    // waiting for has_returned to become true (timeout: 3s)
    waitsFor(function() { $.ajax_get_xml_request.has_returned; }, "the JQuery AJAX GET to return", 3000);
    // TODO: other tests might check size of XML file, whether it is valid XML
    expect($.ajax_get_xml_request.has_returned).toEqual(true);
  }); 

});

Como faço para testar se o retorno de chamada foi chamado? Quaisquer sugestões para blogs / materiais relacionados ao teste de jQuery assíncrono com Jasmine serão muito apreciadas.

mnacos
fonte

Respostas:

234

Acho que existem dois tipos de teste que você pode fazer:

  1. Testes de unidade que falsificam a solicitação AJAX (usando os espiões do Jasmine), permitindo que você teste todo o seu código executado antes da solicitação AJAX e logo depois . Você pode até usar o Jasmine para simular uma resposta do servidor. Esses testes seriam mais rápidos - e não precisariam lidar com o comportamento assíncrono - uma vez que não há nenhum AJAX real acontecendo.
  2. Testes de integração que executam solicitações AJAX reais. Eles precisam ser assíncronos.

Jasmine pode ajudá-lo a fazer os dois tipos de testes.

Aqui está um exemplo de como você pode falsificar a solicitação AJAX e, em seguida, escrever um teste de unidade para verificar se a solicitação AJAX falsificada estava indo para o URL correto:

it("should make an AJAX request to the correct URL", function() {
    spyOn($, "ajax");
    getProduct(123);
    expect($.ajax.mostRecentCall.args[0]["url"]).toEqual("/products/123");
});

function getProduct(id) {
    $.ajax({
        type: "GET",
        url: "/products/" + id,
        contentType: "application/json; charset=utf-8",
        dataType: "json"
    });
}

Para Jasmine 2.0, use:

expect($.ajax.calls.mostRecent().args[0]["url"]).toEqual("/products/123");

como observado nesta resposta

Aqui está um teste de unidade semelhante que verifica se o retorno de chamada foi executado, após uma solicitação AJAX ser concluída com êxito:

it("should execute the callback function on success", function () {
    spyOn($, "ajax").andCallFake(function(options) {
        options.success();
    });
    var callback = jasmine.createSpy();
    getProduct(123, callback);
    expect(callback).toHaveBeenCalled();
});

function getProduct(id, callback) {
    $.ajax({
        type: "GET",
        url: "/products/" + id,
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: callback
    });
}

Para Jasmine 2.0, use:

spyOn($, "ajax").and.callFake(function(options) {

como observado nesta resposta

Finalmente, você sugeriu em outro lugar que pode querer escrever testes de integração que façam solicitações AJAX reais - para fins de integração. Isso pode ser feito usando os recursos assíncronos do Jasmine: waits (), waitsFor () e runs ():

it("should make a real AJAX request", function () {
    var callback = jasmine.createSpy();
    getProduct(123, callback);
    waitsFor(function() {
        return callback.callCount > 0;
    });
    runs(function() {
        expect(callback).toHaveBeenCalled();
    });
});

function getProduct(id, callback) {
    $.ajax({
        type: "GET",
        url: "data.json",
        contentType: "application/json; charset=utf-8"
        dataType: "json",
        success: callback
    });
}
Alex York
fonte
+1 Alex ótima resposta. Na verdade, eu enfrentei um problema semelhante para o qual abri uma pergunta Teste Ajax solicitação usando o objeto Deferred . Você poderia dar uma olhada? obrigado.
Lorraine Bernard
12
Eu realmente gostaria que sua resposta fizesse parte da documentação oficial do Jasmine. Resposta muito clara para um problema que está me matando há alguns dias.
Darren Newton
4
Esta resposta deve realmente ser marcada como a resposta oficial a esta pergunta.
Thomas Amar de
1
A forma atual de obter as informações de chamadas mais recentes é $ .ajax.calls.mostRecent ()
camiblanch
1
Como você implementaria isso para JS ajax simples?
Relojoeiro
13

Veja o projeto jasmine-ajax: http://github.com/pivotal/jasmine-ajax .

É um auxiliar drop-in que (para jQuery ou Prototype.js) faz stubs na camada XHR para que as solicitações nunca saiam. Você pode então esperar tudo o que quiser sobre o pedido.

Em seguida, permite que você forneça respostas de fixação para todos os seus casos e, em seguida, escreva testes para cada resposta que você deseja: sucesso, falha, não autorizado, etc.

Ele tira as chamadas Ajax do reino dos testes assíncronos e fornece muita flexibilidade para testar como seus manipuladores de resposta reais devem funcionar.

user533109
fonte
Obrigado @jasminebdd, o projeto jasmine-ajax parece ser o caminho a percorrer para testar meu código js. Mas e se eu quisesse testar com solicitações reais ao servidor, por exemplo, para testes de conectividade / integração?
mnacos de
2
@mnacos jasmine-ajax é mais útil para testes de unidade, caso em que você deseja evitar especificamente a chamada para o servidor. Se você estiver fazendo testes de integração, provavelmente não é isso que você deseja e deve ser projetado como uma estratégia de teste separada.
Sebastien Martin
7

aqui está um exemplo simples de suíte de teste para um aplicativo js como este

var app = {
               fire: function(url, sfn, efn) {
                   $.ajax({
                       url:url,
                       success:sfn,
                       error:efn
                   });
                }
         };

um conjunto de teste de amostra, que chamará de retorno com base no URL regexp

describe("ajax calls returns", function() {
 var successFn, errorFn;
 beforeEach(function () {
    successFn = jasmine.createSpy("successFn");
    errorFn = jasmine.createSpy("errorFn");
    jQuery.ajax = spyOn(jQuery, "ajax").andCallFake(
      function (options) {
          if(/.*success.*/.test(options.url)) {
              options.success();
          } else {
              options.error();
          }
      }
    );
 });

 it("success", function () {
     app.fire("success/url", successFn, errorFn);
     expect(successFn).toHaveBeenCalled();
 });

 it("error response", function () {
     app.fire("error/url", successFn, errorFn);
     expect(errorFn).toHaveBeenCalled();
 });
});
saltitante
fonte
Valeu cara. Esse exemplo me ajudou muito! Apenas observe que se você estiver usando Jasmine> 2.0, a sintaxe para andCallFake é spyOn (jQuery, "ajax"). And.callFake (/ * sua função * /);
João Paulo Motta
Não tenho certeza se este é um problema de versão, mas recebi um erro em .andCallFake, usei no .and.callFakelugar. Obrigado.
OO de
5

Quando especifico o código ajax com Jasmine, resolvo o problema espionando qualquer função dependente que inicie a chamada remota (como, digamos, $ .get ou $ ajax). Em seguida, recupero os retornos de chamada definidos nele e os testo discretamente.

Aqui está um exemplo que dei recentemente:

https://gist.github.com/946704

Justin Searls
fonte
0

Tente jqueryspy.com Ele fornece uma sintaxe elegante como o jquery para descrever seus testes e permite que retornos de chamada sejam testados após a conclusão do ajax. É ótimo para testes de integração e você pode configurar tempos máximos de espera ajax em segundos ou milésimos de segundo.

Steve Toms
fonte
0

Sinto que preciso fornecer uma resposta mais atualizada, pois o Jasmine está agora na versão 2.4 e algumas funções foram alteradas em relação à versão 2.0.

Portanto, para verificar se uma função de retorno de chamada foi chamada em sua solicitação AJAX, você precisa criar um espião, adicionar uma função callFake a ele e usar o espião como sua função de retorno de chamada. Veja como funciona:

describe("when you make a jQuery AJAX request", function()
{
    it("should get the content of an XML file", function(done)
    {
        var success = jasmine.createSpy('success');
        var error = jasmine.createSpy('error');

        success.and.callFake(function(xml_content)
        {
            expect(success).toHaveBeenCalled();

            // you can even do more tests with xml_content which is
            // the data returned by the success function of your AJAX call

            done(); // we're done, Jasmine can run the specs now
        });

        error.and.callFake(function()
        {
            // this will fail since success has not been called
            expect(success).toHaveBeenCalled();

            // If you are happy about the fact that error has been called,
            // don't make it fail by using expect(error).toHaveBeenCalled();

            done(); // we're done
        });

        jQuery.ajax({
            type : "GET",
            url : "addressbook_files/addressbookxml.xml",
            dataType : "xml",
            success : success,
            error : error
        });
    });
});

Eu fiz o truque para a função de sucesso, bem como para a função de erro, para ter certeza de que Jasmine executará as especificações o mais rápido possível, mesmo se seu AJAX retornar um erro.

Se você não especificar uma função de erro e seu AJAX retornar um erro, você terá que esperar 5 segundos (intervalo de tempo limite padrão) até que o Jasmine emita um erro Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. Você também pode especificar seu próprio tempo limite desta forma:

it("should get the content of an XML file", function(done)
{
    // your code
},
10000); // 10 seconds
pmrotule
fonte