I have created an Env service which wraps up environment information, and I'm currently using $location.host() to determine what environment I'm in. How do I mock that in my tests?
I've read https://groups.google.com/forum/?fromgroups#!topic/angular/F0jFWC4G9hI, but it doesn't seem to work, for example:
describe("Env (environment) service", function() {
var Env;
beforeEach(module('App'));
beforeEach(inject(
['Env', function(e) {
Env = e;
}]
));
describe("for staging", function() {
beforeEach(inject(function($location, $rootScope) {
$location.host("http://staging-site.com");
$rootScope.$apply();
}));
it("returns envrionment as staging", function() {
expect(Env.environment).toEqual("staging");
});
it("returns .isStaging() as true", function() {
expect(Env.isStaging()).toBeTruthy();
});
});
});
I've also tried the $browser variant, but that doesn't work either. Any ideas?
The best way is to use spies IMHO: http://tobyho.com/2011/12/15/jasmine-spy-cheatsheet/
// inject $location
spyOn($location, "host").andReturn("super.domain.com");
var host = $location.host();
alert(host) // is "super.domain.com"
expect($location.host).toHaveBeenCalled();
Syntax for jasmine 2.0 and greater has changed as follows
// inject $location
spyOn($location, "host").and.returnValue("super.domain.com");
var host = $location.host();
alert(host) // is "super.domain.com"
expect($location.host).toHaveBeenCalled();
I had similar problem and have used $injector service. (I don't know if it is the simplest solution, but it worked for me :) )
Since $location cannot be relied in during tests I have prepared my own mock.
First, you need to create a factory method. (Or service or provider if you prefer - see https://gist.github.com/Mithrandir0x/3639232 for comparison):
function locationFactory(_host) {
return function () {
return {
/* If you need more then $location.host(), add more methods */
host: function () {
return _host;
}
};
};
}
Then before you create your 'Env', feed injector with this $location mock:
module(function ($provide) {
$provide.factory('$location', locationFactory('http://staging-site.com'));
});
Now every time your access $location in your code your mock is injected, so it returns whatever you need it to.
More on $provide method is in angular docs
Hope this helps you in the future.
Update: I see one place when you might have gone wrong (or which at least would be wrong in my solution). It seems like you are initiating you 'Env' module (which I guess calculates the data immediately) and only after this you change $location - which might be already too late.
There is a good example in the doc: https://docs.angularjs.org/guide/services
You want to make sure to use $provide before you instantiate the service. I like to use $injector in the testcase, to first decide the location then instantiate the service.
var mockLocation;
beforeEach(function() {
// control the $location.host() function
// which we fake in the testcases.
mockLocation = {
host: jasmine.createSpy()
};
module(function($provide) {
$provide.value('$location', mockLocation);
});
});
then
describe('staging server:', function() {
beforeEach(function() {
// mockLocation.host.andReturn('http://staging-site.com'); // jasmine 1.x
mockLocation.host.and.returnValue('http://staging-site.com');
});
it('returns envrionment as staging', inject(function($injector) {
Env = $injector.get('Env');
expect(Env.environment).toEqual('staging');
}));
});
and
describe('production server:', function() {
beforeEach(function() {
// mockLocation.host.andReturn('prod.example.com'); // jasmine 1.x
mockLocation.host.and.returnValue('prod.example.com');
});
it('returns envrionment as production', inject(function($injector) {
Env = $injector.get('Env');
expect(Env.environment).toEqual('production');
}));
});
Here's another way to mock $location, with Sinon.js
locationMock = {
path: sinon.spy()
};
And an example assertion:
locationMock.path.should.be.calledWith('/home');
Related
I have service that looks like that:
angular.module('app').service('MyService' , function (dependency1, dependency2, dependency3 ...) {
function funcToTest() {
// Do something
}
}
How could I inject a specific dependency to the service? For example, I want to inject only dependency2 to my service and I don't care about the other dependencies.
Unlike unit-testing Angular controllers, we have no way of directly passing dependencies. This is where $provide service comes to rescue!
Here's a sample example:
beforeEach(module('myApp', function ($provide) {
mockDependecy2 = {
mockFunction: function() {}
};
$provide.value('dependency2', mockDependecy2);
}));
Then, you can write your specs normally:
beforeEach(inject(function(_MyService_, ...) {
...
MyService = _MyService_;
}));
describe("...", function() {
it("...", function() {
MyService.funcToTest();
// write expect statements here
})
})
As seen in the example, you can (optionally) enclose them with underscores which are ignored by the injector when the reference name is resolved.
This will automatically inject dependency2 in your service wherever it is used
var dependency2;
beforeEach(function () {
inject(function (dependency2){
dependency2 = dependency2;
});
}
I have an angular factory like this:
.factory('widgetFactory', ['$http', function($http){
function getWidgets(){
return $http.get('http://example.com/api/widgets/')
.then(function(response){
return response;
});
}
return {
getWidgets:getWidgets
};
}])
And I have the following jasmine test:
describe('widgetFactory', function ($q) {
var mockHttp,
fakeResponse = 'response'
beforeEach(function() {
mockHttp = {
get: jasmine.createSpy('get spy').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve(fakeResponse);
return deferred.promise;
})
};
module(function ($provide) {
$provide.value('$http', mockHttp);
});
});
it('should call api when getWidgets is called', inject(function (widgetFactory) {
var result;
widgetFactory.getWidgets().then(function(response){
result = response;
});
expect(mockHttp.post).toHaveBeenCalledWith('http://example.com/api/widgets/');
expect(result).toBe(fakeResponse);
}));
});
But I get the following error: describe does not expect a done parameter
I think it may be to do with how I'm using $q in my test (other examples I've seen have inject(function($q){ ... inside the beforeEach, but I can't due to my use of module inside beforeEach as this then gives me the following error: Injector already created, can not register a module!)
Any ideas?
You can't inject in describe method. Here I've reworked your version to use ngMock and get rid of mockHttp. I hope it explains a little bit how ngMock works
describe('widgetFactory', function () {
var mockHttp,
fakeResponse = 'response',
getWidgetsDefer,
getWidgetsPromise,
$httpBackend,
widgetFactory,
$q;
beforeEach(module('plunker'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$q = $injector.get('$q');
widgetFactory = $injector.get('widgetFactory');
}));
beforeEach(function() {
getWidgetsDefer = $q.defer();
$httpBackend.when('GET', 'http://example.com/api/widgets/')
.respond(getWidgetsDefer);
getWidgetsPromise = widgetFactory.getWidgets();
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should call api when getWidgets is called', inject(function (widgetFactory) {
expect($httpBackend.flush).not.toThrow();
}));
});
You can find plunker here
'done' function is the argument that is expected in Jasmine blocks, but not in describe, that's the meaning of the error. Angular services can't be injected without inject wrapper because Jasmine is unaware of them, and the problem can't be solved just by ignoring this fact.
angular.mock.module is able to mock services with object argument, there's no need to re-invent the wheel.
Unfortunately, mocked services are meant to be self-contained, and it won't solve the problem with $q, so it has to be injected in addition after module:
var $q;
beforeEach(function() {
module({
$http: { ... }
});
inject(function (_$q_) {
$q = _$q_;
});
})
Fortunately, ngMock provides $httpBackend mock, so mocking $http is pointless. In fact, real request shouldn't (and can't) be performed with ngMock. The spec for widget service becomes as slim as that:
widgetFactory.getWidgets();
$httpBackend.expect('GET', 'http://example.com/api/widgets/').respond(fakeResponse);
expect($httpBackend.flush).not.toThrow();
Notice that it doesn't matter if the request was mocked before or after $http.get call, the requests are solved when $httpBackend.flush() is called. And fakeResponse === fakeResponse check can be safely skipped as well.
I have a simple service implemented like this
sameRoof
.factory('dbService', function (localStorageService, backendUpdate) {
return {
checkProfileAndFlat: function () {
return (localStorageService.get('profile') && localStorageService.get('flatshare'));
}
};
});
LocalStorage are ngModules installed with bower.
I am writint unit test
'use strict';
describe('Service: service taking care of asking the local database', function () {
var localStorageService;
var fakeDB = {'profile' : 'testProfile', 'flatshare' : 'flatshare'};
// load the service's module
beforeEach(module('frontApp'));
// instantiate service
var dbService;
beforeEach(inject(function (_dbService_, _localStorageService_) {
dbService = _dbService_;
localStorageService = _localStorageService_;
//mock localStorageService get/add
spyOn(localStorageService,'get').andCallFake(function(key){
return fakeDB[key];
});
}));
it('should check profile and flatshare', function () {
console.log(localStorageService.get('profile'));
expect( dbService.checkProfileAndFlat() ).toBe(false);
});
});
but i am having problems here,
TypeError: 'undefined' is not a function (evaluating 'spyOn ...)
seems like i am implementing in wrong way the spyOn
the answer is
//mock localStorageService get/add
spyOn(localStorageService,'get').and.callFake(function(key){
return fakeDB[key];
});
as i am using jasmine 2.3.4 and jasmine API has changed compared to the one 1.3
The following service uses $q.when to wrap a third-party promise:
// service.js
angular.module('test', [])
.service('pouchdb', function($q, $window) {
var db = new $window.PouchDB('test');
this.info = function() {
return $q.when(db.info.apply(db, arguments));
};
});
Corresponding unit test:
describe('Failing Q when tests', function() {
beforeEach(module('test'));
var $rootScope, pouchdb;
beforeEach(inject(function(_$rootScope_, pouchdb) {
$rootScope = _$rootScope_;
pouchdb = pouchdb;
}));
it('should resolve a promise', function(done) {
// FIXME: never resolves
pouchdb.info()
.then(function(info) {
expect(info).toBeDefined();
})
.finally(done);
$rootScope.$apply();
});
});
pouchdb.info never resolves and Jasmine times out. However, if I manually inject ng, the spec works as expected:
describe('Working Q when tests', function() {
var pouchdb;
beforeEach(function() {
var $injector = angular.injector(['ng', 'test']);
var pouchDB = $injector.get('pouchdb');
pouchdb = pouchDB('db');
});
it('should resolve a promise', function(done) {
pouchdb.info()
.then(function(info) {
expect(info).toBeDefined();
})
.finally(done);
});
});
Could anyone explain why;
The first spec doesn't resolve
The second spec does (injecting ng)
It doesn't need $rootScope.$apply
Whether it's a good pattern to use
Are you using angular-mocks? https://docs.angularjs.org/api/ngMock
The only reason why I think that you'd need to inject 'ng' manually is if there is no ng-app initializing your app, at least according to https://docs.angularjs.org/api/ng/function/angular.module
If you use angular-mocks it takes care of that for you https://github.com/angular/angular.js/blob/master/src/ngMock/angular-mocks.js#L1785
Can't think of any other reason as to why this problem would occur.
I am using Jasmine to test my services. One of my services uses $routeParams as a URL parameter. Now when I test, $routeParams becomes undefined
this is my service code
this.getProjectFunction = function (options) {
$http.get(rootUrl + $routeParams.projectName)
.success(options.success)
.error(options.error);
};
And this is how my test looks like
describe('App Service', function() {
describe('App Service Tests', function(){
var httpBackend, service, optionsSpy, routeParams;
var returnData = [{"id":1,"name":"test"];
beforeEach( module( 'appName' ) );
beforeEach(
inject(
function($httpBackend,projectService,routeParams) {
service = projectService;
optionsSpy = jasmine.createSpyObj('optionsSpy',['success','error','data']);
routeParams = $routeParams;
httpBackend = $httpBackend;
}
)
);
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
//this should get a specific project
it('should call the getAllProjectsFunction function that will return an argument array',
function(){
//set up some data for the http call to return and test later.
httpBackend.whenGET('../'+routeParams.projectName).respond(returnData);
service.getProjectFunction(optionsSpy);
httpBackend.flush();
expect(optionsSpy.success.mostRecentCall.args[0]).toBe(returnData);
}
);
});
});
Im new to Jasmine testing. Thanks for the help have a good day :)
You need to inject $routeParams not routeParams, and then you can set routeParams.projectName = 'foo'; then set up httpBackend.whenGET('../foo').respond(returnData); (../foo may need to be more like /foo since I don't think the "up one folder" command works here.)