2
votes

We have a what we call a CORShttpService, which is basically a wrapper aroung the $http service, but encapsulates some CORS functionality that we need. I'm now writing some tests for a service that has the CORShttpService injected into it. This service has code like this:

CORShttpService({method: requestMethod, url: getUrl(path), data: data}).
    success(function(data, status, headers) {
        //do success stuff
    }).
    error(function(data, status, headers) {
       //do error stuff
    });

I want to mock the call to CORShttpService, but I'm not sure how to go about doing it. I'm using Jasmine, and its spyOn function requires an object to mock the function on the object. My CORShttpService isn't attached to any object, so I don't know how to go about mocking it. Yes, I could use $httpBackend to mock the requests that eventually get set in the CORShttpService, but I don't want it going into that service in the first place. I want to isolate the unit test and simply mock the external calls. Is there any way I can mock this service that is just a function?

2

2 Answers

7
votes

As I thought about this more, the $httpBackend service does provide a lot of functionality for testing requests. As my CORShttpService is a basically a wrapper around $http, I decided I could probably get the most bang for my buck, if I made the mock implementation of CORShttpService simple the $http implementation. Using this documentation to help me, I have the following in my spec:

beforeEach(module(function($provide) {
    $provide.provider('CORShttpService', function() {
        this.$get = function($http) {
            return $http;
        };
    });
}));

So, any of my services wanting the CORShttpService injected, will now basically just have $http injected, and thus allow me to use all the $httpBackend functionality without the concern of the extra functionality found in the CORShttpService itself.

This works for my specific case, but as far as a general solution for mocking services that are just a function, the same kind of thing could probably be done with jasmine.createSpy as mentioned in zbynour's answer. Something like:

beforeEach(module(function($provide) {
    $provide.provider('MyService', function() {
        this.$get = function() {
            return jasmine.createSpy("myService");
        };
    });
}));
2
votes

Yes, as you wrote spyOn can stub a function but it has to be property of already existing object. I also agree it's better to isolate the unit. :)

What you need is jasmine.createSpy. By that you can create a bare spy and define its behavior as usual (using andReturn, andCallFake, etc.).

I think something like the code below (also with jasmine.createSpyObj) cloud be what you want:

it ('should do sth', function() {

    var res = jasmine.createSpyObj('res', [ 'success', 'error' ]);
    res.success.andReturn(res);
    res.error.andReturn(res);
    var corsHttpService = jasmine.createSpy().andReturn(res);

    // call the service using corsHttpService
    anotherService(corsHttpService);

    // assert
    expect(corsHttpService).toHaveBeenCalledWith({
        method: requestMethod, 
        url: url,
        data: data});
    expect(res.success).toHaveBeenCalledWith(jasmine.any(Function));
    expect(res.error).toHaveBeenCalledWith(jasmine.any(Function));
});

In case you want to go further, instead of simple

res.success.andReturn(res);

you can store the callback passed in

var succCallback;
res.success.andCallFake(function(callback) {
        succCallback = callback;
        return res;
    });

and later in the spec you can call the callback to emulate the success result from the corsHttpService:

succCallback('a-data', 'a-status', 'a-header');

Then it's possible to test 'success stuff' of tested service...