I am new to karma unit testing and I have encountered this problem to a simple Ionic Application.
This is my controller:
angular.module('starter').controller('AccountCtrl', function ($scope) {
$scope.settings = {
enableFriends: true
};
$scope.dummyFunction = function () {
console.log("Just do nothing!");
}
});
This is my unit testing file:
describe('AccountCtrl', function () {
var scope, createController;
beforeEach(module('starter'));
console.log("0");
beforeEach(function () {
console.log("1");
})
beforeEach(inject(function ($rootScope, $controller) {
console.log("2");
scope = $rootScope.$new();
controller = $controller('AccountCtrl', {
'$scope': scope
});
}));
it('should do stuff', function () {
console.log("3");
expect("Hello!").toBeDefined();
});
});
At this point, when I run the test I get this:
PhantomJS 2.1.1 (Mac OS X 0.0.0) LOG: 'WARNING: Tried to load angular more than once.'
PhantomJS 2.1.1 (Mac OS X 0.0.0) LOG: '0'
LOG: '1'
LOG: '3'
PhantomJS 2.1.1 (Mac OS X 0.0.0) AccountCtrl should do stuff FAILED
forEach#/Users/eduard.lache/Documents/dummyProjects/unitTestingAPP/www/lib/ionic/js/ionic.bundle.js:13696:24
loadModules#/Users/eduard.lache/Documents/dummyProjects/unitTestingAPP/www/lib/ionic/js/ionic.bundle.js:17883:12
createInjector#/Users/eduard.lache/Documents/dummyProjects/unitTestingAPP/www/lib/ionic/js/ionic.bundle.js:17805:30
workFn#/Users/eduard.lache/Documents/dummyProjects/unitTestingAPP/www/lib/angular-mocks/angular-mocks.js:2353:60
/Users/eduard.lache/Documents/dummyProjects/unitTestingAPP/www/lib/ionic/js/ionic.bundle.js:17923:53
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.004 secs / 0.011 secs)
As you can see from the console.log messages, the inject function is not getting called and the problem is that I don't know why is that and why is this making my test fail as the test expects the string "Hello!" to be defined, which it is.
If I remove the beforeEach that contains the injection the test is successfull.
My goal is to make a test that verifies that $scope.dummyFunction() has been called and that $scope.settings is defined.
i.e.:
it('should do stuff', function () {
var ctrl = controller();
ctrl.dummyFunction();
expect(ctrl.dummyFunction).toHaveBeenCalled();
expect(ctrl.settings).toBeDefined();
});
I'm new testing Node and I just solved the same problem that you had. If it can you help, I solve this problem check all dependencies of app and put they in karma's configuration file. Then, I check that all dependencies ("dev" also) are installed and run karma, and this works! =D
I hope it helps someone.
=)
Related
I'm having issues doing a very simple component test in a NativeScript Angular 2 application. I can't seem to just call "new Component()" and then test it like was shown in this test example. I'm trying an implementation of the Angular 2 TestBed in order to gain access to the component. This doesn't appear to be working either. Has anybody run into a similar problem or have any experience with this?
Component under test:
import { Component } from "#angular/core";
#Component({
selector: "links",
templateUrl: "./components/links/links.component.html"
})
export class LinksComponent {
test = "test";
public constructor() {
}
}
Test:
import "reflect-metadata";
import { LinksComponent } from "../../components/links/links.component";
import { ComponentFixture, TestBed } from '#angular/core/testing';
describe("Links Component", () => {
let comp: LinksComponent;
let fixture: ComponentFixture<LinksComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ LinksComponent ],
});
fixture = TestBed.createComponent(LinksComponent);
comp = fixture.componentInstance;
});
it("will pass because this is a silly test", () => {
expect(comp.test).toEqual("test");
})
});
Log output:
NativeScript / 10.3 (10.3; iPhone) ../../tests/components/links.spec.js at line 0 FAILED
ReferenceError: Can't find variable: Zone
NativeScript / 10.3 (10.3; iPhone): Executed 1 of 0 (1 FAILED) (0 secs / 0 seNativeScript / 10.3 (10.3; iPhone): Executed 1 of 0 (1 FAILED) ERROR (0.008 secs / 0 secs)
CONSOLE LOG file:///app/tns_modules/nativescript-unit-test-runner/main-view-model.js:258:24: NSUTR: completeAck
May 11 16:16:43 --- last message repeated 1 time ---
CONSOLE LOG file:///app/tns_modules/nativescript-unit-test-runner/main-view-model.js:90:28: NSUTR-socket.io: io server disconnect
CONSOLE LOG file:///app/tns_modules/nativescript-unit-test-runner/main-view-model.js:151:24: NSUTR: disregarding second execution
Test run failed.
AFAIK TestBed does not work on NativeScript Angular.
To mock components you need to instance them with new, resolving each dependency.
You should study this repository to be able to do some unit testing on NativeScript.
I'm trying to understand unit testing in Angular2 v2.0.0. I used angular-cli to generate a project, and am running unit tests by launching 'ng test'. The cli generates a sample component including tests.
I have expanded the sample component test by trying to create a host component in which I might test future custom components. Similar to the method I found here:
unit testing using host components
The problem is that after instantiating the test component, it fails a test to look for a bound value inside the test component. It's the last test in the sequence here.
/* tslint:disable:no-unused-variable */
import { TestBed, async } from '#angular/core/testing';
import { AppComponent } from './app.component';
import { Component, Input, OnInit } from '#angular/core';
// let's create a host component to test subcomponents
#Component({
template: `<span>{{testitemname}}</span>`
})
class TestComponent {
testitemname: 'testitem';
testitemtags: ['tag1', 'tag2', 'tag3'];
}
describe('App: Testhostcomp', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent,
TestComponent
],
});
});
it('should create the app', async(() => {
let fixture = TestBed.createComponent(AppComponent);
let app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app works!'`, async(() => {
let fixture = TestBed.createComponent(AppComponent);
let app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app works!');
}));
it('should render title in a h1 tag', async(() => {
let fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('app works!');
}));
// this test fails
it('should render the property value inside the test component', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('span').textContent).toContain('testitem');
}));
});
It fails with the following error:
26 10 2016 10:48:15.456:INFO [Chrome 54.0.2840 (Windows 7 0.0.0)]: Connected on socket /#mcN6GltigqminZ3yAAAA with id 34237141
Chrome 54.0.2840 (Windows 7 0.0.0) App: Testhostcomp should render the property value inside the test component FAILED
Expected '' to contain 'testitem'.
at webpack:///C:/Angular2Projects/testhostcomp/src/app/app.component.spec.ts:49:55 <- src/test.ts:12000:60
at ZoneDelegate.invoke (webpack:///C:/Angular2Projects/testhostcomp/~/zone.js/dist/zone.js:232:0 <- src/test.ts:20985:26)
at AsyncTestZoneSpec.onInvoke (webpack:///C:/Angular2Projects/testhostcomp/~/zone.js/dist/async-test.js:49:0 <- src/test.ts:13735:39)
at ProxyZoneSpec.onInvoke (webpack:///C:/Angular2Projects/testhostcomp/~/zone.js/dist/proxy.js:76:0 <- src/test.ts:14427:39)
Chrome 54.0.2840 (Windows 7 0.0.0): Executed 4 of 4 (1 FAILED) (0 secs / 0.196 secs)
Chrome 54.0.2840 (Windows 7 0.0.0) App: Testhostcomp should render the property value inside the test component FAILED
Expected '' to contain 'testitem'.
at webpack:///C:/Angular2Projects/testhostcomp/src/app/app.component.spec.ts:49:55 <- src/test.ts:12000:60
at ZoneDelegate.invoke (webpack:///C:/Angular2Projects/testhostcomp/~/zone.js/dist/zone.js:232:0 <- src/test.ts:20985:26)
at AsyncTestZoneSpec.onInvoke (webpack:///C:/Angular2Projects/testhostcomp/~/zone.js/dist/async-test.js:49:0 <- src/test.ts:13735:39)
Chrome 54.0.2840 (Windows 7 0.0.0): Executed 4 of 4 (1 FAILED) (0.273 secs / 0.196 secs)
I noticed that when I changed {{testitemname}} to 'testitem', the test passes. So I think it might have something to do with binding? I don't understand why this doesn't work. Thank you in advance for your help.
[1]: https://angular.io/docs/ts/latest/guide/testing.html#!#component-inside-test-host "host components"
It's because you are using 'testitem' as the type not as the value
field: Type; // colon is used for typing
field = value; // equals sign for assignment
your code
testitemname: 'testitem';
testitemtags: ['tag1', 'tag2', 'tag3'];
I have a keyboard shortcut directive that I'm trying to test. It's purpose is to trigger $rootScope.$broadcast() with the event data. In the application it works without any issue but I can't get this darn test to pass as thoroughly as I would like.
(function(){
'use strict';
angular
.module('app.common')
.directive('keyboardShortcuts', keyboardShortcuts);
// #ngInject
function keyboardShortcuts($document, $rootScope){
return {
restrict: 'A',
link
}
function link(scope, el, attrs){
$document.bind('keypress', event => {
console.log('detected keypress');
$rootScope.$broadcast('keyboardShortcut', event);
scope.$digest(); // seems to make no difference
});
}
}
})();
For the unit test, I've added a $scope.$on handler to show that the broadcast is actually being made and listened to, but for some reason the spy isn't doing it's job.
describe('KeyboardShortcuts Directive', function(){
'use strict';
let $element;
let $scope;
let vm;
let $document;
let $rootScope;
const mockKeyboardEvent = {
type: 'keypress',
bubbles: true,
altKey: false,
ctrlKey: false,
shiftKey: false,
which: 106
}
beforeEach(module('app.common'));
beforeEach(inject(function(_$rootScope_, $compile, _$document_){
$element = angular.element('<div keyboard-shortcuts></div>');
$rootScope = _$rootScope_.$new();
$document = _$document_;
$scope = $rootScope.$new();
$compile($element)($scope);
$scope.$digest();
spyOn($rootScope, '$broadcast');
}));
////////////
it('should broadcast when a key is pressed', function(){
$scope.$on('keyboardShortcut', (event, data) => {
console.log('wtf!?');
expect(data.which).toBe(106);
});
$document.triggerHandler(mockKeyboardEvent);
$scope.$digest(); // seems to make no difference
expect($rootScope.$broadcast).toHaveBeenCalled();
});
});
And here is the console output. You can see that the code appears to be working otherwise.
[15:33:57] Starting 'build:common:js'...
[15:33:58] Finished 'build:common:js' after 227 ms
[15:33:58] Starting 'test:common'...
20 09 2016 15:33:58.250:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/
20 09 2016 15:33:58.250:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
20 09 2016 15:33:58.253:INFO [launcher]: Starting browser PhantomJS
20 09 2016 15:33:58.797:INFO [PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Connected on socket /#37Fv2zndO5Z6XAZ8AAAg with id 3035140
..
LOG: 'detected keypress'
LOG: 'wtf!?'
PhantomJS 2.1.1 (Mac OS X 0.0.0) KeyboardShortcuts Directive should broadcast when a key is pressed FAILED
Expected spy $broadcast to have been called.
In the past, when unit testing controllers, I was having trouble like this as well. I was trying to spy on methods that were run in the activate() method which was run immediately (Papa style), I was able to get my spies to work by putting the spyOn(...) method above where the controller was instantiated. In this case, I tried putting my spyOn in all sorts of places within that 'beforeEach' and nothing has seemed to make a difference.
I also tried the obvious solution of putting the expect..broadcast..beenCalled() stuff inside of the $on handler, but that didn't work either, even though that assertion passes and is total proof that a broadcast was made.
I sort of have a feeling that the $rootScope being spied on isn't the same $rootScope that is being injected and working otherwise, but I don't know how that can be.
Proper variable naming is important.
This
$rootScope = _$rootScope_.$new();
is one of wide-spread mistakes in Angular unit tests.
The problem is that a thing that was called $rootScope is just child scope, not root scope. As it appears, it is easy to forget this, so
spyOn($rootScope, '$broadcast');
stubs $broadcast method on some scope.
It should be
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
And
$scope.$digest(); // seems to make no difference
shouldn't be there because jQuery/jqLite events aren't tied to digest cycles.
This is example of my code:
describe('myCtrl functionality', function() {
var driver;
var ptor;
beforeEach(function() {
ptor = protractor.getInstance();
ptor.ignoreSynchronization = true;
browser.ignoreSynchronization = false;
driver = ptor.driver;
});
it('should login', function() {
driver.get('someurl');
driver.findElement(protractor.By.name('username')).sendKeys('admin');
driver.findElement(protractor.By.name('password')).sendKeys('admin');
driver.findElement(protractor.By.css('button[type="submit"]')).click();
});
describe('myCtrl testing', function() {
var $scope;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('dashStoresCtrl', {$scope: $scope});
}));
it('should create "stores" model', function() {
var containerStores = element(by.css('.dashboardStores'));
containerStores.findElements(by.css('.store-item-holder')).then(function(elems) {
expect(elems.length).toEqual($scope.stores.length);
});
});
});
});
And the problem is when i run tests i get TypeError: object is not a function.
That is for the line beforeEach(module('myApp'));
I made research and find out that i need to include angular-mocks.js file in my project and in index.html.
I did it but still get TypeError: object is not a function.
Anyone who can help with this?
Thanks!!!
Protractor tests are end-to-end tests, where NodeJS executes tests that connect to your browser and use it like a numan being would do.
You're trying, in such a protractor test, to use the angularJS API and modules to unit-test a controller. That doesn't make much sense.
Unit tests are typically executed by Karma, inside your browser, and end-to-end protractor tests are typically executed using protractor, inside NodeJS. You shouldn't have a unit test and a protractor test in the same file.
Just starting to learn Angularjs & unit-testing with jasmine...
Following tutorial on http://docs.angularjs.org/tutorial/step_02 to find that the very first unit test (which should pass because scope.phones.length is 3) fails.
INFO [karma]: Karma v0.10.2 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 30.0.1599 (Mac OS X 10.8.5)]: Connected on socket BdjA1lVT9OOo8kgQKLYs
Chrome 30.0.1599 (Mac OS X 10.8.5) PhoneCat controllers PhoneListCtrl should create "phones" model with 3 phones FAILED
ReferenceError: PhoneListCtrl is not defined
at null.<anonymous> (/Applications/MAMP/htdocs/angular-phonecat/test/unit/controllersSpec.js:12:22)
Chrome 30.0.1599 (Mac OS X 10.8.5): Executed 2 of 2 (1 FAILED) (0.37 secs / 0.033 secs)
So basically, it is stating that PhoneListCtrl is not defined. However the app is working perfectly, and I don't really know where to start considering I am at the beginning of the tutorial!
Here is my unit test which is the default from the tutorial.
test/unit/controllerSpec.js
'use strict';
/* jasmine specs for controllers go here */
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
beforeEach(module('phonecatApp'));
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
expect(scope.phones.length).toBe(3);
});
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', {$scope:scope});
expect(scope.phones.length).toBe(3);
}));
});
});
app/js/controller.js
'use strict';
/* Controllers */
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function PhoneListCtrl($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'},
{'name': 'Motorola XOOM™ with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.'},
{'name': 'MOTOROLA XOOM™',
'snippet': 'The Next, Next Generation tablet.'}
];
$scope.hello = "Hello World";
});
config/karma.conf.js http://pastebin.com/PPWjSmyJ
You have 2 unit tests in the example (2 it blocks). They look like they are supposed to do the same thing, but only the second actually creates your controller.
When you defined the controller in angular, it is not a globally available object that you can initialise with new Controller(...). You must request it from angular.
Your second test (which seems to be passing) does this by injecting the $controller service which will do perform any actions needed to set up and request the controller.
inject(function($controller) { ... });
It then creates the controller using this service like so
var scope = {},
ctrl = $controller('PhoneListCtrl', {$scope:scope});
In your first test you try to just use the PhoneListCtrl variable directly. As the error says, this doesn't exist unless you define a variable with that name in your function.
I have just noticed that failing test in the tutorial. This is specifically for if you have defined a controller on the global namespace. For example
function PhoneListCtrl($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'},
{'name': 'Motorola XOOM™ with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.'},
{'name': 'MOTOROLA XOOM™',
'snippet': 'The Next, Next Generation tablet.'}
];
$scope.hello = "Hello World";
};
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
The test would then work because you have function defined globally that is used as a controller, so you are able to test it without paying attention to the fact it is a controller. This means if you try to use other services you will have to inject them yourself and perform any actions that $controller would do for you.