In my module.run block it is calling a method on a service I have made. When running my tests I want it to reference a mock service instead of the real one which is making http requests. I am currently trying to test a controller, not the actual run block itself - how can I inject the mock service into the run function? I have tried using $provide.factory but it doesn't seem to do anything and is still loading the service as normal.
I am using Jasmine to write my tests.
app.js
angular.module("app")
.run(function(MyService) {
MyService.log("starting app");
});
test.js
describe("MyController", function() {
beforeEach(function() {
module(function ($provide) {
$provide.factory("MyService", { log: function(){} });
});
});
// I want module 'app' to execute its run function using injected value for MyService
beforeEach(module("app"));
beforeEach(inject(function($controller, $rootScope) {
MyController = $controller("MyController", { $scope: $rootScope.$new() });
}));
...........
});
In this case is important order.
You need load your app first
beforeEach(module("app"));
and then overwrite MyService definition.
beforeEach(
module({
"MyService": {
log: function(message) {
console.log("MyFakeService called: " + message);
}
}
})
);
Otherwise app service implementation is last registred and used.
working example is here - look to the console http://plnkr.co/edit/BYQpbY?p=preview
Related
I'm trying to write unit tests for an angular service with jasmine/karma. I have a similar service test, which works just fine. But this one has some additional dependencies, is in a different module and just doesn't find the service with the inject.
The service looks something like this. bService is in the same module, but commonFactory and commonService are in another module, say commonModule.
(function () {
'use strict';
angular
.module('myService')
.service('aService', aService);
aService.$inject = [
'commonFactory',
'commonService'
'bService'
];
function aService (
commonFactory,
commonService,
bService
) {
};
return {
codeIWantToTest: CodeIWantToTest;
}
function CodeIWantToTest () {
console.log('surprise!');
}
})();
My jasmine test looks like:
describe('myService.aService', function () {
'use strict';
var aService;
// I tried adding beforeEach(module('commonModule')); too, but that didn't do anything
beforeEach(module('myService'));
beforeEach(function() {
inject(function(_aService_) {
console.log('getting aService');
aService = _aService_;
});
});
it('tests my service is defined', function() {
expect(myService).toBeDefined();
});
});
This test fails. myService isn't defined and the console.log in the inject function doesn't ever fire. My karma.conf.js basically lists the dependencies in the order that they're injected into the service, then adds the service then the test.
What would cause the inject to not grab the service? What am I missing? I mentioned I have a similar test for commonService and it works just fine. So I'm baffled.
Another dev on my team found the solution and I wanted to post it as an answer for the future people. I had a feeling it was a dependency problem, and it was. While we were loading all of the JS stuff correctly, the template that the component uses was loading another js dependency. So to fix this for jasmine, we had two different solutions:
at the top of the component test file, we could add:
beforeEach(function () {
module(function ($provide) {
$provide.constant('myMissingDependency', {
// do things to load the dependency here
});
});
});
In our case it was a translation library
The other solution was to add a 'shim' file into the unit test directory and load it with karma.config.js ahead of the tests. That looked like:
(function() {
angular
.module('MyService')
.constant('myMissingDependency', Object.freeze({
// things to load the dependency
}));
})();
I wasn't able to switch to Chrome because we're using Docker and I couldn't get the tests to run locally to run Chrome. So adding a second set of eyes to this was what I needed.
I have the following code in my spec file
beforeEach(function () {
module('app');
inject(function ($injector) {
user = $injector.get('app.user');
});
});
user is undefined, and isn't being injected. So I want to make sure that the app module actually loaded.
If the module is not loaded, you get $injector:nomod error. If the module is loaded but the service cannot be found, you get $injector:unpr error. It is as easy as that. There is always a breadcrumb trail, no need to probe Angular to know if it fails silently or not.
Just make sure you're using the right module name. You can use beforeEach to load your module. Also, with $injector you can get an instance of your service or controller you're trying to test:
'use strict';
describe('MyControllerName', function () {
var MyControllerName;
beforeEach(module('myAppMomduleName'));
beforeEach(inject(function ($injector) {
MyControllerName = $injector.get('MyControllerName');
}));
it('should create an instance of the controller', function () {
expect(MyControllerName).toBeDefined();
});
});
I am running into some challenges unit testing $log.
I wanted to try a simple test that took the value I injected and tested. I am not hooking the spec up to a restful call yet. I am using angular mocks. Here are my before each statements. I have the module defined through angular mocks. Some of what I have been testing has came from this blog
http://www.bradoncode.com/blog/2015/06/08/ngmock-fundamentals-log/.
In my devDependencies
"angular": "^1.5.0",
"angular-mocks": "^1.5.0",
I am using gulp to set up the tests.
gulp.task('test:jasmine', function (done) { // move to return async when execution metrics done
gulp.src('./Scripts/tests/modules/utility/services/ha-http.service.jasmine.test.js')
.pipe(jasmine({
verbose: true
}))
.on('error', gutil.log)
.on('end', function () {
console.log('jasmine end');
done();
});
});
So I know the versions of angular and mocks are matched up.
beforeEach(function () {
angular.mock.module('ha.module.utility');
angular.mock.inject(function ($httpBackend, haHttpService) {
http = $httpBackend;
service = haHttpService;
});
});
beforeEach(inject(function (_$log_) {
$log = _$log_;
}));
afterEach(function () {
http.flush();
http.verifyNoOutstandingExpectation();
http.verifyNoOutstandingRequest();
});
The test itself is just making sure I have $log working.
it('should call logs', function () {
$log.info('it worked');
expect($log.info.logs).toContain(['it worked']);
});
However I am returning
ReferenceError: inject is not defined
************* Update ***********
I did set up $log in the mock module
angular.mock.module('ha.module.utility', function ($provide) {
$provide.decorator('$log', function ($delegate) {
return $delegate;
});
});
angular.mock.inject(function ($httpBackend, haHttpService, $log) {
http = $httpBackend;
service = haHttpService;
console.log($log);
});
I am getting the output I want in the console.log($log) when I run my gulp test. However, $log still returns
ReferenceError: $log is not defined
in the spec.
ReferenceError: inject is not defined
means that inject global was not defined, literally. It is defined in angular-mocks.js on condition.
The problem is caused by testing rig and not the specs. angular-mocks.js may not be loaded, or it may be loaded before Jasmine.
I am trying to mock a factory within one of my angularjs modules. The full angularjs application is
angular.module('administration', ['administrationServices'])
The dependency:
var module = angular.module('administrationServices')
contains a factory service:
module.factory('Example', [function(){
return{
list:function(){
return ['This is real']
}
}
}])
This is the service I am attempting to override in my protractor e2e test. The actual test looks something like this:
describe('Example page', function(){
beforeEach(function() {
var mock = function(){
// get the module and provide the mock
var module = angular.module('administrationServices').config(['$provide', function($provide){
$provide.factory('Example',[function(){
return {
list: function(){
return ['This is a Mock Test']
}
}
}])
}])
}
// override the mock service
browser.addMockModule('administrationServices', mock)
})
it('should go to the page and see mock text', function() {
// code that goes to page and checks if the service works
// ...
})
})
The issue I'm having occurs when I $ protractor conf.js, I get the error:
Error while running module script administrationServices: [$injector:nomod] http://errors.angularjs.org/1.3.4/$injector/nomod?p0=administrationServices
This is where I'm confused. Every blog post about protractor's addMockModule uses similar syntax it seems. There are other factory services in administrationServices and those seem to get overwritten because the app can't open to the page due to those services (user and group services) not working.
Anyways, if somebody has seen this and can help direct me in the right direction, that would help; I am fairly new to mock services, protractor and e2e testing.
I think the problem is that your mock function does not return anything. It doesn't share the new module outside the function scope.
var mock = function(){
// get the module and provide the mock
return angular.module('administrationServices').config(['$provide', function($provide){
$provide.factory('Example',[function(){
return {
list: function(){
return ['This is a Mock Test'];
}
}
}])
}])
};
// override the mock service
browser.addMockModule('administrationServices', mock)
Just make it return the new module and it should be fine.
I have been writing some Jasmine unit tests in Angular. In the first example I'm testing a controller.
myApp.controller('MyCtrl', function($scope, Config){
...
});
I have a configuration service (Config) that keeps configuration from the database and is injected into my controller. As this is a unit test, I want to mock out that configuration service altogether, rather than allowing execution to pass through it and using $httpBackend. Examples I found taught me about a $controller function I can use like this, in order to get an instance of my controller with my mocks injected in place of the usual collaborator:
beforeEach(inject(function($controller, $rootScope){
var scope = $rootScope.$new();
var configMock = {
theOnlyPropertyMyControllerNeeds: 'value'
};
ctrl = $controller('MyCtrl', {
$scope:scope,
Config: configMock
});
}));
But I also have other services that use the Config service. To help unit test them, I assumed there would be a similar $service function I could use to instantiate a service with whatever mocks I want to provide. There isn't. I tried $injector.get, but it doesn't seem to let me pass in my mocks. After searching for a while, the best I could come up with in order to instantiate a service in isolation (avoid instantiating its collaborators) is this:
beforeEach(function() {
mockConfig = {
thePropertyMyServiceUses: 'value'
};
module(function($provide) {
$provide.value('Config', mockConfig);
});
inject(function($injector) {
myService = $injector.get('MyService');
});
});
Is this the right way? It seems to be overriding the entire application's definition of the Config service, which seems maybe like overkill.
Is it the only way? Why is there no $service helper method?
For unit testing, it is common that you override a service for the sake of testing. However, you can use $provide to override an existing service instead of using inject, as long as you load the application before hand.
Assuming that you created Config using something like:
angular.moduel('...', [...]).factory('Config', function (...) {...});
If so, try this:
...
beforeEach(module("<Name of you App>"));
beforeEach(
module(function ($provide) {
$provide.factory('Config', function (...) {...});
});
);
...
After that, when you initialise your controller, it will get the mocked Config.