function in controller:
angular.module('myApp').controller('MyController', function(){
$scope.f = function($event){
$event.preventDefault();
//logic
return data;
}
})
describe('MyController', function(){
'use strict';
var MyController,
$scope;
beforeEach(module('myApp'));
beforeEach($inject(function($rootScope, $controller){
$scope = $rootScope.$new();
MyController = $controller('MyController', {
$scope: $scope
})
}));
})
it('should...', function(){
//fire event and expect data
})
$scope.f function is used in directive, it can be executed by ng-click="f($event)"
what is right way for fire event in unit test?
Short Answer
You don't need to fire the event. You have access to the scope, which has the function you want to test. This means you just execute the function, then assert. It will look something like this:
it('should call preventDefault on the given event', function(){
var testEvent = $.Event('someEvent');
$scope.f(testEvent);
expect(testEvent.isDefaultPrevented()).toBe(true);
});
See the following:
jQuery Event Object
event.isDefaultPrevented()
Full Spec
Also - your it block should be inside your describe block, so that it has access to the $scope field. It should look more like this:
describe('MyController', function(){
'use strict';
var MyController,
$scope;
beforeEach(module('myApp'));
beforeEach($inject(function($rootScope, $controller){
$scope = $rootScope.$new();
MyController = $controller('MyController', {
$scope: $scope
})
}));
it('should call preventDefault on the given event', function(){
var testEvent = $.Event('someEvent');
$scope.f(testEvent);
expect(testEvent.isDefaultPrevented()).toBe(true);
});
})
A Note About Structure
Don't be afraid to use the describe blocks to structure your tests. Imagine you had another function on the $scope called f2, then you would probably want to partition your spec file up more like this:
describe('MyController', function(){
'use strict';
var MyController,
$scope;
beforeEach(module('myApp'));
beforeEach($inject(function($rootScope, $controller){
$scope = $rootScope.$new();
MyController = $controller('MyController', {
$scope: $scope
})
}));
describe('$scope', function() {
describe('.f()', function() {
// tests related to only the .f() function
});
describe('.f2()', function() {
// tests related to only the .f2() function
});
});
})
This has the benefit that when a test fails, the error message you see is constructed based on the hierarchy of describe blocks. So it would be something like:
MyController $scope .f() should call preventDefault on the given
event
Related
I am working to initiate a controller sits inside a directive. I have some tests I need to run but right now I am not able to access the controller with ng-Mock.
describe('hero Directive', function () {
var $compile,
$rootScope,
$scope,
element,
ctrl;
beforeEach(function () {
angular.mock.module('ha.module.core');
angular.mock.inject(function (_$compile_, _$rootScope_, _$controller_, $templateCache) {
$compile = _$compile_;
element = angular.element("<div exlore-hereo></div");
$compile(element)($rootScope);
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
ctrl = _$controller_('ExploreHeroController', { $scope: $scope });
console.log(ctrl)
$scope.$digest();
});
});
afterEach(function () {
// need to remove the element element.remove();
});
describe('directive controller', function () {
it('should dispatch call $emit with $methodsBound', function () {
//spyOn($scope, '$emit');
spyOn($scope, 'ControllerName');
//expect(scope.$emit).toHaveBeenCalledWith('$methodsBound');
//expect(ctrl).toHaveBeenCalled();
});
});
});
I created an element compiled it and called the $digest method.
The error that I got was
Argument 'scope' is required.
So I tried spying on the it with jasmine
spyON($scope, 'ControllerName');
My controller inside of my directive is pretty basic.
var ControllerName = function($scope) {
$scope.$emit('$method');
}
It seems like I need a spy, but I am not sure why the one I created does not work.
You can try to spy on $scope, but note that ControllerName is not a member of the $scope object.
However, $emit is....
The thing is, that you call $emit in the controllers constructos, therefore you have to spy on it before:
beforeEach(function () {
...
$scope = $rootScope.$new();
spyOn($scope, '$emit');
ctrl = _$controller_('ExploreHeroController', { $scope: $scope });
...
});
describe('directive controller', function () {
it('should dispatch call $emit with $methodsBound', function () {
expect($scope.$emit).toHaveBeenCalledWith('$methodsBound');
});
});
I have the following...
app.controller('testCtrl', function(testService){
testService.doSomething();
});
app.service('testService', function(){
this.doSomething = function(){...};
});
I want to use Jasmine to ensure doSomething is called once and only once. I seem to be having some trouble doing this.
Also, I am currently grabbing my controller from a compiled element like this...
var element = angular.element('<my-test-directive />');
controller = view.controller('testCtrl');
So extra appreciation if it fits with this sort of formatting
Update
I tried this...
describe("Testing", function () {
var $rootScope,
$scope,
$compile,
testService,
view,
$controller;
beforeEach(module("app"));
function createController() {
return $controller('testCtrl', {
$scope: scope,
testService:testService
});
}
function SetUpScope(_$controller_, _$compile_, _$rootScope_, _testService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$controller = _$controller_;
testService = _testService_;
spyOn(testService, 'doSomething');
}
SetUpScope.$inject = ["$controller","$compile", "$rootScope", "testService"];
beforeEach(inject(SetUpScope));
it("On intitialization, the controller should register itself with the list service", function(done){
createController();
scope.$digest();
expect(workOrderService.doSomething).toHaveBeenCalled();
})
});
It seems to work
It is probably better to test controller in isolation and use Jasmine spies for this:
spyOn(testService, 'doSomething');
expect(testService.doSomething.calls.count()).toEqual(0);
Something like this should work in the actual test.
describe('testCtrl function', function() {
describe('testCtrl', function() {
var $scope, testService;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller, _testService_) {
$scope = $rootScope.$new();
testService = _testService_;
spyOn(testService, 'doSomething');
$controller('MyController', {$scope: $scope});
}));
it('should call testService.doSomething()', function() {
expect(testService.doSomething.calls.count()).toEqual(1);
});
});
});
Here is a quick plunkr http://plnkr.co/edit/Swso4Y
Depending on which version of Jasmine you are using you might need to use
expect(testService.doSomething.calls.length).toEqual(1);
I have a controller test that depends on the Angular $routeParams service:
var $routeParams, MainCtrl, scope;
beforeEach(inject(function ($controller, $rootScope, $injector, $templateCache) {
scope = $rootScope.$new();
$routeParams = $injector.get('$routeParamsMock');
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$routeParams: $routeParams,
});
}));
it('should load a pg from $routeParams', function(){
scope.userData = {};
$routeParams._setPg('PG_FIRST');
scope.$digest();
timeout.flush();
expect(scope.userData.pg).toBe(0);
$routeParams._setPg('PG_SECOND');
scope.$digest();
timeout.flush();
expect(scope.userData.pg).toBe(1);
});
$routeParamsMock:
!(function(window, angular){
'use strict';
angular.module('vitaApp')
.service('$routeParamsMock', function() {
var _pg = null;
return{
pg: _pg,
_setPg: function(pg){
_pg = pg;
}
}
});
})(window, window.angular);
When debugging the test, I was surprised to find out that $routeParamsMock.pg was returning null every single time, even though I called _setPg with a different value.
Is it because null is considered a primitive (with a type of object...), and thus passed by value?, or perhaps because Angular is copying the object that is passed to the $controller service?.
The solution I am looking for is preferably one that won't require to instanciate different controllers per different test scenerios.
eg:
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$routeParams: {'pg': 'PG_FIRST'},
});
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$routeParams: {'pg': 'PG_SECOND'},
});
The thing is, what you don't want to do, is probably the best solution you have. A mock makes sense when what you want to mock is kinda complex. Complex dependency with methods, lot of states, etc. For a simple object like $routeParams it makes all the sense of the world to just pass a dummy object to it. Yes it would require to instantiate different controllers per test, but so what?
Structure your tests in a way that makes sense, makes it readable and easy to follow.
I suggest you something like:
describe('Controller: Foo', function() {
var $controller, $scope;
beforeEach(function() {
module('app');
inject(function($rootScope, _$controller_) {
$scope = $rootScope.$new();routeParams = {};
$controller = _$controller_;
});
});
describe('With PG_FIRST', function() {
beforeEach(function() {
$controller('Foo', { $scope: $scope, $routeParams: {'PG': 'PG_FIRST'}});
});
it('Should ....', function() {
expect($scope.something).toBe('PG_FIRST');
});
});
describe('With PG_SECOND', function() {
beforeEach(function() {
$controller('Foo', { $scope: $scope, $routeParams: {'PG': 'PG_SECOND'}});
});
it('Should ....', function() {
expect($scope.something).toBe('PG_SECOND');
});
});
});
With a good test organization, I can say that I like this test easy to follow.
http://plnkr.co/edit/5Q3ykv9ZB7PuGFMfWVY5?p=preview
I have a service that synchronously returns data to a controller:
angular.module('app').controller(function($scope, myService) {
$scope.foo = myService.getFoo();
});
This works just fine in the browser. In my unit tests, $scope.foo is undefined:
beforeEach(function () {
module('app');
myService = jasmine.createSpyObj('myService', ['getFoo']);
inject(function($controller, $rootScope) {
$scope = $rootScope.$new();
ctrl = $controller('ModelSliderCtrl', {
myService: myService,
$scope: $scope
});
});
});
it('should have foo on the scope', function() {
myService.getFoo.and.returnValue({});
expect(myService.getFoo).toHaveBeenCalled(); // PASS
$scope.$digest();
expect($scope.foo).toBeDefined(); // FAIL - $scope.foo is undefined
});
This does work in both the browser and tests:
angular.module('app').controller(function($scope, myService) {
$scope.init = function() {
$scope.foo = myService.getFoo();
};
$scope.init();
});
.
it('should have foo on the scope', function() {
myService.getFoo.and.returnValue({});
$scope.init();
expect(myService.getFoo).toHaveBeenCalled(); // PASS
expect($scope.foo).toBeDefined(); // PASS
});
I'd like to believe I'm fairly well-versed in Angular, Jasmine and JavaScript. I've also asked some colleagues who are equally puzzled.
Can anyone explain to me what is going on here?
You are setting up a mock
it('should have foo on the scope', function() {
myService.getFoo.and.returnValue({});
after your controller has been instantiated. It's too late to set up the mock by then, do it before instantiating your controller since you are executing init() right away.
myService = jasmine.createSpyObj('myService', ['getFoo']);
myService.getFoo.and.returnValue({});
inject(function($controller, $rootScope) {
I have a controllers.js file that looks like this:
angular.module('MyApp.controllers', []).
controller('MyCtrl', [function() {
$scope.type = "default";
}]);
and, controllersSpec.js looks like this:
describe('controllers', function(){
beforeEach(module('MyApp.controllers'));
describe('MyCtrl', function() {
it('should have a property named "type" whose default value is "default"', inject(function() {
expect(MyCtrl.type).toBe("default");
}));
});
});
How can I test that the MyCtrl controller has a type property and that property's default value is the "default" string?
Furthermore, is this type of test even worthwhile or should I rewrite it? If I should rewrite it, then how?
Because you are testing a property of your $scope and not your Controller function, you need to mock the entire creation of a Ctrl with a mocked $scope.
var scope, controller;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {$scope: scope});
}));
it('should have a default type when created', function() {
expect(scope.type).toBe("Default")
});