I have a directive, where, in certain case I use
angular.extend(dist, src)
Now I would like to test this case and check, if angular.extend is called.
I'm trying to use spyOn
spyOn(angular, 'extend')
And then in test
expect(angular.extend).toHaveBeenCalled()
Not sure I can do it at all, but I decided to give it a try.
Thanks for any help.
EDIT:
Here is my test, edited in accordance with your advise.
it('should create new scope and extend config if config is passed to directive', function() {
var spy = jasmine.createSpy('extendSpy').and.callThrough();
angular.extend = spy;
timeout.flush();
_.forEach(scope.accordionConfig, function(configItem) {
if (configItem.config) {
expect(angular.extend).toHaveBeenCalled();
}
});
});
In beforeEach hook I don't have anything special, just assigning config, creating some other preparation for rest tests and compiling the directive.
Here is a snippet from link function which I'm trying to test
if (scope.format === 'directive') {
if (scope.config) {
newScope = $rootScope.$new();
angular.extend(newScope, scope.config);
}
scope.content = $compile(scope.content)(newScope || scope);
}
console log the value of angular.extend before the assertion, it should be an instance of jasmine.spy, if it is not, there is a problem in the way that you create the spy, and we will need more context, perhaps your full code could help.
I assume you create the hook somewhere in the beforeEach hook or on one of the other hooks?
Try the following code:
var spy = jasmine.createSpy('extendSpy').and.callThrough();
angular.extend = spy;
expect(spy).toBeCalledWith(dist, src);
Related
I added this code to my component controller to focus an input and it worked great in the browser but it broke all of my template tests. I thought I could just flush the $timeout and all would be well but it's not.
vm.$onInit = init;
function init(){
focusInput();
}
function focusInput(){
$timeout(function(){
$document[0]
.querySelector('md-autocomplete-wrap')
.querySelector('input')
.focus();
}, 0);
}
However, in my unit test Jasmine is reporting that .querySelector is not available because the result of the first querySelector is null in the test environment.
it('should render', function(){
var wrap, searchBarDirective, $scope;
$scope = $rootScope.$new();
searchBarDirective = $compile(angular.element(template))($scope);
$scope.$digest();
$timeout.flush();
wrap = searchBarDirective.find('md-autocomplete-wrap')[0];
expect(wrap).toBeDefined();
});
It's obvious to me that $document doesn't contain the rendered directive and thus the second querySelector fails. But why doesn't $document contain the directive?
I tried mocking querySelector with spyOn($document[0], "querySelector").and.returnValues($document[0],$document[0]) but that doesn't get me past the focus. Thinking I have lot my way here.
* Revised *
I think that it is important to continue to use $document but I decided to drop the querySelector for the jqLite find method.
function focusInput(){
$timeout(function(){
var input;
try {
// can throw an error if the first find fails
input = $document.find('md-autocomplete').find('input');
}
catch (e) {
angular.noop(e);
}
if(input && angular.isFunction(input.focus)) {
input.focus();
}
}, 0);
}
The test I changed per comments to below. I do have Karma load jquery to make testing easier which allows me to search for :focus
beforeEach(function(){
element = angular.element(template);
$document[0].body.appendChild(element[0]);
$scope = $rootScope.$new();
});
afterEach(function(){
element[0].remove();
});
it('should be focused', function(){
var input, searchBarDirective;
searchBarDirective = $compile(element)($scope);
$scope.$digest();
$timeout.flush();
input = searchBarDirective.find(':focus')[0];
expect(input).toBeDefined();
});
The reason why your querySelector call works in the browser, but not in tests is that you are creating a DOM element with angular.element, but you are never attaching it to the document. There are two ways to address this:
First, you could simply do this. Instead of:
searchBarDirective = $compile(angular.element(template))($scope);
Do:
let element; // declare this in the describe block so it is available later
element = angular.element(template);
document.body.appendChild(element[0]);
searchBarDirective = $compile(element)($scope);
And then do this:
afterEach(() => element[0].remove());
But, that's a bit messy. You should not be manipulating global scope (ie- the document) in your unit tests unless you have to. It would be better in your non-test code to avoid accessing the document and instead access a scope element, or some other DOM element that you can also mock in your tests. This will be a bit harder to do since it may require re-architecting your code a bit. In general though, in order to make modular and testable code, you want to avoid accessing the document object as much as possible.
I have the following method in my controller:
$scope.onWindowResize = function() {
var dWidth= $('#div1').width();
var left = $('#div1').position().left;
$('.myClass1').css({'width':dWdth});
$('.myClass2').css({'left': left});
}
I am trying to test the method onWindowResize() with the below code.
it('onWindowResize()', inject(function () {
scope.onWindowResize();
scope.$apply();
}));
But I get this error message:
"message": "'undefined' is not an object (evaluating '$('#div1').position().left')"
and
dWidth is null
I have sinon spy, but not sure how to use it here. Can I use it somehow to spy on it, or is there a better way to test this method? I think I need to mock "$('#div1')", but no idea how to do that. Any help is highly appreciated.
First, there is typo in your code, you're using dWidth wrong in css function like this adjWdth.
You can create a spy on $ / jQuery function like this and spy for css function
var cssSpy = jasmine.createSpy('cssSpy');
$ = jasmine.createSpy().andCallFake(function(){
return {
width:function(){return 200;},
position: function(){return{left:500};},
css: cssSpy
};
});
$scope.onWindowResize();
expect(cssSpy).toHaveBeenCalledWith({'width':200});
expect(cssSpy).toHaveBeenCalledWith({'left':500});
As you can see, I'm overwriting $ / jQuery function itself and returning an object with the properties/methods you use in the onWindowResize function. And also a spy for css function. andCallFake is old version of jasmine the new one is .and.callFake(). Please confirm if it works for you and study it well. You can do it in beforeEach block to be reusable and mock all keys you want to return when you selecting a DOM element ;). By the way, it's not recommended to have a DOM manipulation code inside controller, it should be in a directive's link function.
Hello stackoverflow community.
I am working on an Angular project (1.5.6), using a component structure and currently writing some unit tests. I am still learning a lot about unit tests – especially in relation with Angular – and was hoping I can ask you for help for the following issue:
I try to test a component, that receives a callback method from it's parent component. I am trying to mock the method foo (see below the code example). And unfortunately does this method call the parent controller.
So when I try to test it, it complains, that the method is undefined. Then I thought I could mock it with spyOn, but then I get the error Error: <spyOn> : foobar() method does not exist
So I think I am unable to mock that method.
Module:
angular.module("myApp")
.component("sample", {
"templateUrl": "components/sample/sample.html",
"controller": "SampleController",
"controllerAs": "sampleCtrl",
"bindings": {
"config": "<",
"foobar": "&"
}
})
.controller("SampleController",
["$scope",
function($scope) {
this.isActive = true;
this.foo = function() {
// do stuff
this.isActive = false;
// also do
this.foobar();
};
}
);
Unit Test
describe("Component: SampleComponent", function() {
beforeEach(module("myApp"));
var sampleComponent, scope, $httpBackend;
beforeEach(inject(function($componentController, $rootScope, _$httpBackend_) {
scope = $rootScope.$new();
sampleComponent = $componentController("sample", {
"$scope": scope
});
$httpBackend = _$httpBackend_;
}));
it("should do set isActive to false on 'foo' and call method...", function() {
spyOn(sampleComponent, "foobar")
expect(sampleComponent.isActive).toBe(true);
expect(sampleComponent).toBe("");
expect(sampleComponent.foobar).not.toHaveBeenCalled();
sampleComponent.foo();
expect(sampleComponent.foobar).toHaveBeenCalled();
expect(sampleComponent.foobar.calls.count()).toBe(1);
expect(sampleComponent.isActive).toBe(false);
});
});
I hope I didn't add any bugs to this, but this above is approximately what I am trying to do. Any suggestions are welcome and if the approach is wrong or more information needed, please let me know!
After the help from #estus (see comments in question) - I learned that I can use createSpy to resolve this issue.
it("should do set isActive to false on 'foo' and call method...", function() {
sampleComponent.foobar = jasmine.createSpy();
expect(sampleComponent.isActive).toBe(true);
expect(sampleComponent).toBe("");
expect(sampleComponent.foobar).not.toHaveBeenCalled();
sampleComponent.foo();
expect(sampleComponent.foobar).toHaveBeenCalled();
expect(sampleComponent.foobar.calls.count()).toBe(1);
expect(sampleComponent.isActive).toBe(false);
});
Some additional sources I used were:
http://angular-tips.com/blog/2014/03/introduction-to-unit-test-spies/
How does the createSpy work in Angular + Jasmine?
I'm having troubling testing a controller's value that's set within a promise returned by a service. I'm using Sinon to stub the service (Karma to run the tests, Mocha as the framework, Chai for assertions).
I'm less interested in a quick fix than I am in understanding the problem. I've read around quite a bit, and I have some of my notes below the code and the test.
Here's the code.
.controller('NavCtrl', function (NavService) {
var vm = this;
NavService.getNav()
.then(function(response){
vm.nav = response.data;
});
})
.service('NavService', ['$http', function ($http) {
this.getNav = function () {
return $http.get('_routes');
};
}]);
Here's the test:
describe('NavCtrl', function () {
var scope;
var controller;
var NavService;
var $q;
beforeEach(module('nav'));
beforeEach(inject(function($rootScope, $controller, _$q_, _NavService_){
NavService = _NavService_;
scope = $rootScope.$new();
controller = $controller;
}));
it('should have some data', function () {
var stub = sinon.stub(NavService, 'getNav').returns($q.when({
response: {
data: 'data'
}
}));
var vm = controller("NavCtrl", {
$scope: scope,
NavService: NavService
});
scope.$apply();
stub.callCount.should.equal(1);
vm.should.be.defined;
vm.nav.should.be.defined;
});
});
The stub is being called, i.e. that test passes, and vm is defined, but vm.nav never gets data and the test fails. How I'm handling the stubbed promise is, I think, the culprit. Some notes:
Based on reading elsewhere, I'm calling scope.$apply to set the value, but since scope isn't injected into the original controller, I'm not positive that will do the trick. This article points to the angular docs on $q.
Another article recommends using $timeout as what would "actually complete the promise". The article also recommends using "sinon-as-promised," something I'm not doing above. I tried, but didn't see a difference.
This Stack Overflow answer use scope.$root.$digest() because "If your scope object's value comes from the promise result, you will need to call scope.$root.$digest()". But again, same test failure. And again, this might be because I'm not using scope.
As for stubbing the promise, I also tried the sinon sandbox way, but results were the same.
I've tried rewriting the test using $scope, to make sure it's not a problem with the vm style, but the test still fails.
In the end, I could be wrong: the stub and the promise might not be the problem and it's something different and/or obvious that I've missed.
Any help is much appreciated and if I can clarify any of the above, let me know.
Sorry but a quick fix was all that you needed:
var stub = sinon.stub(NavService, 'getNav').returns($q.when({
response: {
data: 'data'
}
}));
Your promise is resolved to object containing response.data not just data
Checkout this plunk created from your code: https://plnkr.co/edit/GL1Xuf?p=preview
The extended answer
I have often fallen to the same trap. So I started to define the result returned from a method separately. Then if the method is async I wrap this result in a promise like $q.when(stubbedResult) this allow me to, easily run expectations on the actual result, because I keep the stubbed result in a variable e.g.
it('Controller should have some data', function () {
var result = {data: 'data'};
var stub = sinon.stub(NavService, 'getNav').returns($q.when(result));
var vm = controller(/* initController */);
scope.$apply();
stub.callCount.should.equal(1);
vm.nav.should.equal(result.data)
})
Also some tests debugging skill will come in handy. The easiest thing is to dump some data on the console just to check what's returned somewhere. Working with an actual debugger is preferable of course.
How to quickly catch mistakes like these:
Put a breakpoint at the $rootScope.apply() line (just before it is executed)
Put a breakpoint in the controller's NavService.getNav().then handler to see whether it is called and what it was called with
Continue with the debugger to execute the $rootScope.$apply() line. Now the debugger should hit the breakpoint set at the previous step - that's it.
I think you should use chai-as-promised
and then assert from promises like
doSomethingAsync().should.eventually.equal("foo");
or else use async await
it('should have some data', async function () {
await scope.$apply();
});
you might need to move then getNav() call in init kinda function and then test against that init function
I am totally new to testing in AngularJS. I have setup karma, and am now attempting to test a certain function in a factory I have written.
Here is a snippet of my factory:
app.factory('helpersFactory', ['constants', function (constants) {
return {
someFunction: function() {
},
is24x24Icon: function (iconNum) {
return ((iconNum >= 10090 && iconNum <= 10125) ;
}
};
}]);
I then have this test:
describe('Factory: helpersFactory', function () {
beforeEach(module('ppMobi'));
var fct;
beforeEach(inject(function ($factory) {
fct = $factory('helpersFactory');
}));
it('should detect iconNum 10090 is a 24 x 24 icon', function () {
var iconNum = 10090;
var is24x24Icon = fct.is24x24Icon(iconNum);
expect(is24x24Icon).toBeTruthy();
});
});
I get an error from Karma telling me it cannot read 'is24x24icon' of undefined. Therefore I can only assume my factory has not been created properly during the test. I do have a dependency on constants in the factory used by other functions. This is just an angular.constant() I have setup on my main application module.
I have found some other posts, but am unsure how to proceed, do I need to inject my constants dependency into my test?
Kind of new myself but I think you need to use the underscore name underscore trick to inject your factory:
var fct;
beforeEach(inject(function (_helpersFactory_) {
fct = _helpersFactory_;
}));
This blog uses mocha but I found it useful and the Karma stuff should be the same: https://www.airpair.com/angularjs/posts/testing-angular-with-karma
And yes you will need to inject the constants as well (the link shows how) but your posted code does not seem to use constants so you won't need it for this particular test.