2

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?

dnc253
  • 39,967
  • 41
  • 141
  • 157

2 Answers2

7

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");
        };
    });
}));
dnc253
  • 39,967
  • 41
  • 141
  • 157
2

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...

zbynour
  • 19,747
  • 3
  • 30
  • 44
  • Thank you for your response. This is all great information, but you have the line `anotherService(corsHttpService);`. The `CORShttpService` is automatically injected into the service I want to test. There's no call to the service with the `CORShttpService` as a parameter. I'm not sure how to go about injecting the mock `CORShttpService` in this service I want to test. – dnc253 Jan 28 '13 at 15:57
  • So finally you found the solution... Since there is no mention about how to inject the spy into the spec in your question I thought you have major problem with mocking a service which is a function in general rather than performing the injection. Your own answer also seems interesting but actually not working for me. :-( – zbynour Jan 29 '13 at 08:42
  • Well, my issue was how the mock the function itself (which you answered), but as part of that, having the mock injected everywhere needed, instead of the original service just being injected. What's not working for you in my answer? – dnc253 Jan 29 '13 at 15:46
  • Basically injecting `$provide` into my spec. Now I've found it works well but doesn't work in my particular situation (bit specific but valid). I have `describe` with `beforeEach(module(function ($provide) {...`, no `it` inside this `describe`, just only inner `describe` containing some `it`s. In outer `describe` declaring `var foo = null` and in (outer) `beforeEach` assigning some value. The `foo` value is used in `it` of inner `describe` and it fails with "`foo` is null". The only problem is outer `beforeEach(module(...`. Without it, i.e. `beforeEach(function () {` works perfectly fine. – zbynour Jan 30 '13 at 10:08
  • Apparently, since there is no `it` in outer `describe`, its `beforeEach` is not called. When some `it` added it works fine as well as witout `it` in outer div but with *simple* outer `beforeEach(function() { ...`. That means in case of `beforeEach(module(function($provide) { ...` it isn't called if no `it` on current `describe` block (although normaly *is* called). Bit tricky... :-) Do you have any idea please? – zbynour Jan 30 '13 at 11:02
  • In fact, the problem i have described above is bit unusual and surely can be workarounded. Regardless of that your answer was inspirational for me and works in general, so +1 for that! – zbynour Jan 31 '13 at 08:01