Given a directive that has an external controller:
.directive('d1', function () {
return {
controller: 'd1controller',
restrict: 'E',
link: function ($scope, $element, $attributes, $controller) {
$controller.doStuff();
}
};
});
How do I mock the d1controller controller in the d1 directive's unit tests?
My attempts:
I tried with $provide as when mocking a service:
beforeEach(module('app', function ($provide) {
ctrlMock = jasmine.createSpyObj('ctrlMock', ['doStuff']);
$provide.value('d1controller', ctrlMock );
}));
And I also tried with $controllerProvider
beforeEach(module('app', function ($controllerProvider) {
ctrlMock = jasmine.createSpyObj('ctrlMock', ['doStuff']);
$controllerProvider.register('d1controller', ctrlMock);
}));
I'm the OP. It turns out that using $controllerProvider works. You have to pass it a constructor; not an instance.
beforeEach(module('app', function ($controllerProvider) {
$controllerProvider.register('d1controller', function Mock(){
this.doStuff = function(){};
});
}));
I have had decent luck creating a mock module, that contains all of my mocked services/controllers and then including that mock module after my app module. It will essentially override all of the services/controllers you have in your mock module.
Create your mock module, and your controller to mock in that module.
appMock = angular.module('appMock', []);
appMock.controller('ControllerToMockCtrl', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
}]);
In your test.
beforeEach(function(){
module('app');
module('appMock');
});
This blog is a good example of this strategy. http://southdesign.de/blog/mock-angular-js-modules-for-test-di.html#using-the-mock
Related
I want to test my Angular component which is syntactically based on John Papa's styleguide:
'use strict';
angular.module('MyModule')
.component('MyCmpnt', MyCmpnt())
.controller('MyCtrl', MyCtrl);
function MyCmpnt() {
return {
restrict: 'E',
templateUrl: 'myPath/myTemplate.html',
bindings: {
foo: '=',
bar: '<'
},
controller: 'MyCtrl',
controllerAs: 'vm'
};
}
MyCtrl.$inject = ['MyService'];
function MyCtrl (MyService) {
// controller logic
}
As you can see I want to inject MyService into the controller and spy in a function on that very service.
My test code:
'use strict';
describe('component: MyCmpnt', function () {
var $componentController,
MyService;
beforeEach(module('MyModule'));
beforeEach(module(function ($provide) {
$provide.value('MyService', MyService);
spyOn(MyService, 'serviceFunc').and.callThrough();
}));
beforeEach(inject(function (_$componentController_) {
$componentController = _$componentController_;
}));
it('should initiate the component and define bindings', function () {
var bindings = {
foo: 'baz',
bar: []
};
var ctrl = $componentController('MyCmpnt', null, bindings);
expect(ctrl.foo).toBeDefined();
});
});
However, this setup lets me run into the following error:
TypeError: undefined is not a constructor (evaluating '$componentController('MyModule', null, bindings)')
The code above has $componentController('MyModule'..., and there is no MyModule component.
MyService variable is undefined when spyOn(MyService... is called. This will throw an error prevent the application from being bootstrapped correctly.
If testing rig uses PhantomJS, this may lead to error suppression in beforeEach blocks, for correct error reporting Chrome Karma launcher is recommended.
If the problem is that MyService is undefined at the point where mocked service is defined, it can be defined in-place as a stub:
beforeEach(module(function ($provide) {
$provide.value('MyService', {
serviceFunc: jasmine.createSpy().and.callThrough()
});
}));
I'm testing a directive ('planListing') that has a dependency on a service called 'planListingService'. This service has a dependency to another service called 'ajax' (don't shoot the messenger for the bad names).
I'm able to compile the directive, load its scope and get the controller WITH A CAVEAT. As of now I am being forced to mock both services 'planListingService' and 'ajax' otherwise I will get an error like this:
Error: [$injector:unpr] Unknown provider: ajaxProvider <- ajax <- planListingService
http://errors.angularjs.org/1.3.20/$injector/unpr?p0=ajaxProvider%20%3C-%20ajax%20%3C-%20planListingService
I thought that because I was mocking up the 'planListingService' that I wouldn't have to actually bother with any implementation nor any dependencies of this service. Am I expecting too much?
Here is the code in a nutshell:
planListing.js
angular.module('myApp')
.directive('planListing', planListing)
.controller('planListingCtrl', PlanListingCtrl);
function planListing() {
var varDirective = {
restrict: 'E',
controller: PlanListingCtrl,
controllerAs: 'vm',
templateUrl: "scripts/directives/planListing/planListing.html";
}
};
return varDirective;
}
PlanListingCtrl.$inject = ['planListingService'];
function PlanListingCtrl(planListingService) {
...
}
planListingService.js
angular.module('myApp')
.factory('planListingService', planListingService);
planListingService.$inject = ['$q', 'ajax'];
function planListingService($q, ajax) {
...
}
ajax.js
angular.module('myApp')
.factory('ajax', ['backend', '$browser', 'settings', '$http', '$log',
function (backend, $browser, settings, $http, $log) {
...
planListing.spec.js
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
module('myApp');
module('my.templates');
beforeEach(module(function ($provide){
// This seems to have no effect at all, why?
$provide.service('planListingService', function () {
this.getAllPricePlans=function(){};
});
// I don't get the error if I uncomment this:
// $provide.service('ajax', function ($q) {
// this.getAllPricePlans=function(){};
// });
}));
beforeEach(function() {
module('myApp');
module('my.templates');
});
beforeEach(angular.mock.inject(function (_$compile_,_$rootScope_,_$controller_){
$compile=_$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
el = angular.element('<plan-listing></plan-listing>');
scope = $rootScope.$new();
$compile(el)(scope);
scope.$digest();
ctrl = el.controller('planListing');
scope = el.isolateScope() || el.scope();
vm = scope.vm;
}));
describe('testing compilation / linking', function (){
it('should have found directive and compiled template', function () {
expect(el).toBeDefined();
expect(el.html()).not.toEqual('');
expect(el.html()).toContain("plan-listing-section");
});
});
it('should have a defined controller',function(){
expect(ctrl).toBeDefined();
});
it('should have a defined scope',function(){
expect(ctrl).toBeDefined();
});
});
So why is that I need to mock up the 'ajax' service even though I am mocking up 'planListingService' which is the one calling the 'ajax' service?
Thanks!
I have been there... feels like bad start But i think your directive is depend on the service and you need to inject it in order to directive can work with this, Just by calling directive it doesn't mean that it's going to inject it in your test. It will look for it and if it's not injected it will give you error
you could do so before testing your directive
beforeEach(inject(function ($injector) {
yourService = $injector.get('yourService');
})
For documentation purposes, here is the answer (thanks #estus for noticing this):
Indeed the problem was related to the incorrect initialization of my modules. Instead of this:
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
module('myApp');
module('my.templates');
...
I should've done this:
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
beforeEach(module('myApp'));
beforeEach(module('my.templates'));
...
After that things started working again as expected.
I've been trying to get started with unit testing in angular with karma and jasmine, and i've been pulling my hair out trying to wrap my head around how to test controllers with dependencies. I tried mocking a spy with a jasmine spyObj and registering it in the beforeEach hook, but for some reason the spy isn't being recognized.
Here's the code:
angular.module('testModule', [])
.controller('TestController', [
'$scope',
'TestService',
function ($scope, TestService) {
$scope.data = TestService.load();
}])
.factory('TestService', function () {
return {
load: function(){
return "foo";
}
}
});
and here's the test
describe('TestController', function() {
var $controller, $scope, TestService;
beforeEach(module('testModule'), function($provide){
TestService = jasmine.createSpyObj("TestService", ["load"]);
TestService.load.andReturn("bar");
$provide.value("TestService", TestService)
});
beforeEach(inject(function(_$controller_, $rootScope, _TestService_) {
$scope = $rootScope.$new();
TestService = _TestService_;
$controller = _$controller_('TestController', {
$scope: $scope,
TestService: TestService
});
}));
it('should set $scope.data to bar when TestService.load is called', function() {
expect(TestService.load).toHaveBeenCalled();
expect($scope.data).toEqual("bar");
}); });
Both assertions in the test fail.
I get 'Error: Expected a spy, but got Function' when i call expect(TestService.load).toHaveBeenCalled();
and if I call expect($scope.data).toEqual("bar"), I get Expected 'foo' to equal 'bar'. "Foo" is coming from the actual service, not the spy object.
Thanks for your help.
Instead of jasmine.createSpyObj, it will be easier to use the existing service that the $injector provides and then just mock the single method. You can achieve this with spyOn instead:
describe('TestController', function() {
var $controller, $scope, TestService;
beforeEach(module('testModule'));
beforeEach(inject(function(_$controller_, $rootScope, _TestService_) {
$scope = $rootScope.$new();
TestService = _TestService_;
spyOn(TestService, 'load').and.returnValue('bar');
$controller = _$controller_('TestController', {
$scope: $scope,
TestService: TestService
});
}));
it('should set $scope.data to bar when TestService.load is called', function() {
expect(TestService.load).toHaveBeenCalled();
expect($scope.data).toEqual("bar");
});
});
In your beforeEach you are injecting in _TestService_ and then overwriting the one you declared in the previous beforeEach via:
TestService = _TestService_;
Remove that code and your test should succeed.
Also there is no need to do this:
$provide.value("TestService", TestService)
Basically you're trying to use Angular's dependency injection when you're manually injecting things which is unnecessary.
The project I am working on makes use of socket.io for some of the components of the UI.
I am trying to write unit tests for this particular section of the application. I am using: angular-socket-io and angular-socket.io-mock to mock the server side component.
I am using everything at the simplest level, so I have my factory:
myapp
.factory('notify', function (socketFactory) {
return socketFactory();
});
This is the controller
myapp
.controller('NotificationsCtrl', function ($scope, notify) {
$scope.items = []
notify.emit("loadItems",{},function(res){
$scope.items = res.res
});
});
and finally the unit test:
describe('Controller: NotificationsCtrl', function () {
// load the controller's module
beforeEach(module('app'));
var NotificationsCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
NotificationsCtrl = $controller('NotificationsCtrl', {
$scope: scope
});
}));
it('The scope.items should change somehow', function() {
expect(scope.items.length).toEqual(3);
});
});
I cannot realize what is missing to make this working.
How should I change my code to make it happen?
Thanks
Can’t figure out how to make controller tests working. I am playing with ngStart seed project. So I forked the repository (https://github.com/Havrl/ngStart ) and want to create a very basic unit test.
My controller test file:
define(function() {
"use strict";
describe("the contactcontroller", function () {
var contactController, scope;
beforeEach(function () {
module("contact");
inject(["ContactController", function (_contactController) {
contactController = _contactController;
}]);
});
it("should give me true", function () {
expect(true).toBe(true);
});
});
});
But that is not working.
What am I missing?
as answered in the following question the controller needs to be manually instantiated with a new scope:
how to test controllers created with angular.module().controller() in Angular.js using Mocha
Additionally the project you are using (its mine :-) ) is defining the controllers inside route definitions and not with a call to angular.controller(...).
The downside is that the controllers are not known by name to angularJS (afaik), so the code from the answer above would not work:
ctrl = $controller("ContactController", {$scope: scope });
Instead you have to load the controller explicitely with requireJS inside your test file and give the function to the $controller(..) call, like this:
define(["ContactController"], function(ContactController) {
"use strict";
describe("the contactcontroller", function () {
var contactController, scope;
beforeEach(function () {
module("contact");
inject(["$rootScope", "$controller", function ($rootScope, $controller) {
scope = $rootScope.$new();
contactController = $controller(ContactController, {$scope: scope});
}]);
});
....
});