3

I have the following angular controller

function IndexCtrl($scope, $http, $cookies) { //get list of resources $http.get(wtm.apiServer + '/v1/developers/me?access_token=' + $cookies['wtmdevsid']). success(function(data, status, headers, config) { // snip }). error(function(data, status, headers, config) { // snip }); $scope.modal = function() { // snip } return; } 

What I am trying to do is mock the get method on the $http service. Here's my unit test code:

describe('A first test suite', function(){ it("A trivial test", function() { expect(true).toBe(true); }); }); describe('Apps', function(){ describe('IndexCtrl', function(){ var scope, ctrl, $httpBackend; var scope, http, cookies = {wtmdevsid:0}; beforeEach(inject(function($injector, $rootScope, $controller, $http) { scope = $rootScope.$new(); ctrl = new $controller('IndexCtrl', {$scope: scope, $http: $http, $cookies: cookies}); spyOn($http, 'get'); spyOn(scope, 'modal'); })); it('should create IndexCtrl', function() { var quux = scope.modal(); expect(scope.modal).toHaveBeenCalled(); expect($http.get).toHaveBeenCalled(); }); }); }); 

When I run this I get ReferenceError: wtm is not defined.

wtm is a global object and of course it wouldn't be defined when I run my test because the code that it is declared in is not run when I run my test. What I want to know is why the real $http.get function is being called and how do I set up a spy or a stub so that I don't actually call the real function?

(inb4 hating on globals: one of my coworkers has been tasked with factoring those out of our code :) )

3 Answers 3

4

You need to wire up the whenGET method of your $httpBackend in advance of your test. Try setting it up in the beforeEach() function of your test... There is a good example here under "Unit Testing with Mock Backend".

Sign up to request clarification or add additional context in comments.

4 Comments

I get the same problem and I think the reason is because I can't predict ahead of time what url is going to be passed to GET(). I want to do something like $httpBackend.when('GET', '*').respond({}); but I'm guessing the * doesn't work as a wildcard in this context because I'm still getting the same ReferenceError: wtm is not defined.
Now that I think about this a little more it doesn't matter what path I tell GET to expect. When it runs my controller code it's going to try to parse that wtm variable. Unless there's someway to mock the GET function so that it actually ignores the arguments passed to it in the code, I'm going to have to refactor that global wtm varibale out before I can actually test this controller.
Actually it looks like for now I can use the whenGET function you suggested, blesh, and for now declare and define wtm in the scope of my test.
whenGET takes strings and RegExp objects, this means you can specify wildcards as ypu please, FYI
4

I suggest all globals used the way you described here should be used through the $window service.

All global variables that are available, such as as window.wtm, will also be available on $window.atm.

Then you can stub out your wtm reference completely and spy on it the same way you already described:

 var element, $window, $rootScope, $compile; beforeEach(function() { module('fooApp', function($provide) { $provide.decorator('$window', function($delegate) { $delegate.wtm = jasmine.createSpy(); return $delegate; }); }); inject(function(_$rootScope_, _$compile_, _$window_) { $window = _$window_; $rootScope = _$rootScope_; $compile = _$compile_; }); }); 

Comments

1

Maybe you could create a custom wrapper mock around $httpBackend that handles your special needs.

In detail, Angular overwrites components of the same name with a last-come first-served strategy, this means that the order you load your modules is important in your tests.

When you define another service with the same name and load it after the first one, the last one will be injected instead of the first one. E.g.:

apptasticMock.service("socket", function($rootScope){ this.events = {}; // Receive Events this.on = function(eventName, callback){ if(!this.events[eventName]) this.events[eventName] = []; this.events[eventName].push(callback); } // Send Events this.emit = function(eventName, data, emitCallback){ if(this.events[eventName]){ angular.forEach(this.events[eventName], function(callback){ $rootScope.$apply(function() { callback(data); }); }); }; if(emitCallback) emitCallback(); } }); 

This service offers the exact same interface and behaves exactly like the original one except it never communicates via any socket. This is the service we want to use for testing.

With the load sequence of angular in mind, the tests then look like this:

describe("Socket Service", function(){ var socket; beforeEach(function(){ module('apptastic'); module('apptasticMock'); inject(function($injector) { socket = $injector.get('socket'); }); }); it("emits and receives messages", function(){ var testReceived = false; socket.on("test", function(data){ testReceived = true; }); socket.emit("test", { info: "test" }); expect(testReceived).toBe(true); }); 

});

The important thing here is that module('apptasticMock') gets executed after module('apptastic'). This overwrites the original socket implementation with the mocked one. The rest is just the normal dependency injection procedure.

This article I wrote could be interesting for you, as it goes into further details.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.