¿Cómo verifico los eventos de jQuery AJAX con Jasmine?

114

Estoy tratando de usar Jasmine para escribir algunas especificaciones de BDD para solicitudes básicas de jQuery AJAX. Actualmente estoy usando Jasmine en modo independiente (es decir, a través SpecRunner.html). He configurado SpecRunner para cargar jquery y otros archivos .js. ¿Alguna idea de por qué lo siguiente no funciona? has_returned no se convierte en realidad, incluso aunque el "yuppi!" alerta aparece bien.

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);
  }); 

});

¿Cómo pruebo que se ha llamado a la devolución de llamada? Cualquier sugerencia a blogs / material relacionado con la prueba async jQuery con Jasmine será muy apreciada.

mnacos
fuente

Respuestas:

234

Supongo que hay dos tipos de pruebas que puedes hacer:

  1. Pruebas unitarias que falsifican la solicitud AJAX (utilizando los espías de Jasmine), lo que le permite probar todo su código que se ejecuta justo antes de la solicitud AJAX y justo después . Incluso puedes usar Jasmine para falsificar una respuesta del servidor. Estas pruebas serían más rápidas, y no necesitarían manejar el comportamiento asincrónico, ya que no hay ningún AJAX real.
  2. Pruebas de integración que realizan solicitudes AJAX reales. Estos deberían ser asincrónicos.

Jasmine puede ayudarlo a realizar ambos tipos de pruebas.

Aquí hay una muestra de cómo puede falsificar la solicitud AJAX y luego escribir una prueba unitaria para verificar que la solicitud AJAX falsificada iba a la URL correcta:

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 en su lugar:

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

como se indica en esta respuesta

Aquí hay una prueba unitaria similar que verifica que se ejecutó su devolución de llamada, luego de que una solicitud AJAX se complete correctamente:

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 en su lugar:

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

como se indica en esta respuesta

Finalmente, ha insinuado en otra parte que es posible que desee escribir pruebas de integración que realicen solicitudes AJAX reales, con fines de integración. Esto se puede hacer usando las características asíncronas de Jasmine: waits (), waitsFor () y 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
fuente
+1 Alex gran respuesta. En realidad, me enfrenté a un problema similar para el que abrí una pregunta Prueba la solicitud Ajax usando el objeto Aplazado . ¿Podrías echar un vistazo? Gracias.
Lorraine Bernard
12
Realmente desearía que tu respuesta fuera parte de la documentación oficial de Jasmine. Respuesta muy clara a un problema que me mata desde hace unos días.
Darren Newton
4
Esta respuesta realmente debería marcarse como la respuesta oficial a esta pregunta.
Thomas Amar
1
La forma actual de obtener la información de llamadas más reciente es $ .ajax.calls.mostRecent ()
camiblanch
1
¿Cómo implementarías eso para JS simple ajax?
Relojero
13

Mira el proyecto jasmine-ajax: http://github.com/pivotal/jasmine-ajax .

Es un asistente de inserción que (ya sea para jQuery o Prototype.js) se almacena en la capa XHR para que las solicitudes nunca salgan. A continuación, puede esperar todo lo que desee sobre la solicitud.

Luego le permite proporcionar respuestas fijas para todos sus casos y luego escribir pruebas para cada respuesta que desee: éxito, fracaso, no autorizado, etc.

Saca las llamadas Ajax del ámbito de las pruebas asincrónicas y le proporciona mucha flexibilidad para probar cómo deberían funcionar sus controladores de respuesta reales.

usuario533109
fuente
Gracias @jasminebdd, el proyecto jasmine-ajax parece el camino a seguir para probar mi código js. Pero, ¿qué sucede si quisiera probar con solicitudes reales al servidor, por ejemplo, para pruebas de conectividad / integración?
mnacos
2
@mnacos jasmine-ajax es principalmente útil para pruebas unitarias, en cuyo caso específicamente desea evitar la llamada al servidor. Si está realizando pruebas de integración, probablemente esto no sea lo que desea y debería diseñarse como una estrategia de prueba independiente.
Sebastien Martin
7

aquí hay un conjunto de pruebas de ejemplo simple para una aplicación js como esta

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

un conjunto de pruebas de muestra, que llamará a la devolución de llamada según la 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();
 });
});
skipy
fuente
Gracias amigo. ¡Este ejemplo me ayudó mucho! Solo tenga en cuenta que si está utilizando Jasmine> 2.0, la sintaxis de andCallFake es spyOn (jQuery, "ajax"). And.callFake (/ * su función * /);
João Paulo Motta
No estoy seguro si se trata de un problema de versión, pero recibí un error .andCallFake, se usó en su .and.callFakelugar. Gracias.
OO
5

Cuando especifico el código ajax con Jasmine, resuelvo el problema espiando cualquier función dependiente que inicie la llamada remota (como, por ejemplo, $ .get o $ ajax). Luego recupero las devoluciones de llamada configuradas y las pruebo discretamente.

Aquí hay un ejemplo que obtuve recientemente:

https://gist.github.com/946704

Justin Searls
fuente
0

Pruebe jqueryspy.com Proporciona una elegante sintaxis similar a jquery para describir sus pruebas y permite que las devoluciones de llamada prueben después de que se haya completado el ajax. Es excelente para las pruebas de integración y puede configurar los tiempos de espera máximos de Ajax en segundos o milésimas de segundo.

Steve Toms
fuente
0

Siento que necesito proporcionar una respuesta más actualizada ya que Jasmine está ahora en la versión 2.4 y algunas funciones han cambiado desde la versión 2.0.

Por lo tanto, para verificar que se haya llamado a una función de devolución de llamada dentro de su solicitud AJAX, debe crear un espía, agregarle una función callFake y luego usar el espía como su función de devolución de llamada. Así es 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
        });
    });
});

He hecho el truco para la función de éxito, así como la función de error, para asegurarme de que Jasmine ejecutará las especificaciones lo antes posible, incluso si su AJAX devuelve un error.

Si no especifica una función de error y su AJAX devuelve un error, tendrá que esperar 5 segundos (intervalo de tiempo de espera predeterminado) hasta que Jasmine arroje un error Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. También puede especificar su propio tiempo de espera de esta manera:

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