Lozzer
Lozzer

Reputation: 385

Mocking jQuery ajax calls with Jasmine

I am using Jasmine 2.5.2 to write unit tests for code that performs Ajax requests using jQuery 3.1.1 . I would like to mock out the Ajax call, providing my own response status and text.

I am using the Jasmine ajax plug-in (https://github.com/pivotal/jasmine-ajax).

Following the example on https://jasmine.github.io/2.0/ajax.html, which uses the XMLHttpRequest object, works fine.

describe("mocking ajax", function() {
    describe("suite wide usage", function() {
        beforeEach(function() {
            jasmine.Ajax.install();
        });

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

        it("specifying response when you need it", function() {
            var doneFn = jasmine.createSpy("success");

            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function(args) {
                if (this.readyState == this.DONE) {
                    doneFn(this.responseText);
                }
            };

            xhr.open("GET", "/some/cool/url");
            xhr.send();
            expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url');
            expect(doneFn).not.toHaveBeenCalled();

            jasmine.Ajax.requests.mostRecent().respondWith({
                "status": 200,
                "contentType": 'text/plain',
                "responseText": 'awesome response'
            });
            expect(doneFn).toHaveBeenCalledWith('awesome response');                      
        });
    });
});

NB: this differs slightly from the documented example, had to change jasmine.Ajax.requests.mostRecent().response() to jasmine.Ajax.requests.mostRecent().respondWith().

When I use jQuery Ajax, the doneFn is never called.

describe("mocking ajax", function() {
    describe("suite wide usage", function() {
        beforeEach(function() {
            jasmine.Ajax.install();
        });

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

        it("specifying response when you need it", function() {
            var doneFn = jasmine.createSpy("success");

            $.ajax({
                method: "GET",            
                url: "/some/cool/url"})
            .done(function(result) {
                doneFn(result);
            });
            expect(doneFn).toHaveBeenCalledWith('awesome response');                      
        });
    });
});

Jasmine states that

Jasmine-Ajax mocks out your request at the XMLHttpRequest object, so should be compatible with other libraries that do ajax requests.

$.ajax returns a jqXHR from 1.4.x and not XMLHttpRequest - does this break support with Jasmine Ajax?

Upvotes: 0

Views: 8526

Answers (3)

Pavlo Oliinyk
Pavlo Oliinyk

Reputation: 400

In addition to danronmoon answer(Approach B):

I added options.success(testData) inside spyOn($, 'ajax') to trigger this.processResponseData

Fn to test:

 var objUnderTest = {};
 objUnderTest.makeRequest(requestUrl) {
    $.ajax(requestUrl)
      .then(response => {
        this.processResponseData(response.data[0]) // get data from first array item
      })
  }

Test:

  describe('objUnderTest.makeRequest', () => {
    const testData = {data: [{'text': 'this a a fake response'}]};

    let processResponseData;
    beforeAll(() => {
      spyOn($, 'ajax').and.callFake(function (options) {
        options.success(testData)
        return $.Deferred().resolve(testData).promise();
      });
      processResponseData = spyOn(objUnderTest, 'processResponseData')
    })

    it('should make ajax call', () => {
      objUnderTest.makeRequest({success: stockTicker.processResponseData});

      expect(processResponseData).toHaveBeenCalled();
    });
  });

Upvotes: 0

Lozzer
Lozzer

Reputation: 385

When mocking the $.ajax object, the parameter passed in can be used directly to trigger a success or failure, without going through the promise interface.

spyOn($, 'ajax').and.callFake(function (options) {
    var result = undefined, status, xhr;
    options.success(result, status, xhr);
});

spyOn($, 'ajax').and.callFake(function (options) {
    var xhr = undefined, status, error;
    options.error(xhr, status, error);
});

Upvotes: 1

Winter Soldier
Winter Soldier

Reputation: 2685

Here are a couple of ways you could mock ajax in your jasmine tests

Approach A:

  • Using mock ajax.js, you can mock the global xhr object as described in the doc
  • However you'll need to specify a spy on the success function and let it callThrough (as seen in the code below)
  • See it in action here

    describe('ajax test suite', function() {
    
      beforeEach(function() {
        jasmine.Ajax.install();
      });
    
      afterEach(function() {
        jasmine.Ajax.uninstall();
      });
    
      it('sample test', function() {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(args) {
          if (this.readyState == this.DONE) {
            testObj.successFunction(this.responseText);
          }
        };
        spyOn(testObj, 'successFunction').and.callThrough();
        xhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1");
        xhr.send();
        expect(jasmine.Ajax.requests.mostRecent().url).toBe('https://jsonplaceholder.typicode.com/posts/1');
        expect(testObj.successFunction).not.toHaveBeenCalled();
        jasmine.Ajax.requests.mostRecent().respondWith({
          "status": 200,
          "contentType": 'text/plain',
          "responseText": 'awesome response'
        });
        expect(testObj.successFunction).toHaveBeenCalledWith('awesome response');
      });
    });
    

Approach B:

  • Directly mocking the $.ajax object. Be it get/post/load or any ajax flavor from jquery, you can simply mock the $.ajax as shown below.

    var testObj = {
      ajaxFunction : function(url){     
       $.ajax({url : url}).done(this.successFunction.bind(this));
      },
      successFunction : function(data){
       console.log(data);
     }
    }
    
    describe('ajax test suite', function(){
        it('sample test', function(){
         testObj.ajaxFunction('https://jsonplaceholder.typicode.com/posts/1');
         spyOn($, 'ajax').and.callFake(function(e) {
              return $.Deferred().resolve({'text':'this a a fake response'}).promise();
         });
         spyOn(testObj, 'successFunction').and.callThrough();
         testObj.ajaxFunction('https://jsonplaceholder.typicode.com/posts/1');
         expect(testObj.successFunction).toHaveBeenCalledWith({'text':'this a a fake response'});
      });
    });
    

Upvotes: 8

Related Questions