I try to test some component with DI. I looking in the common resources like stack/forums etc. But there is no correct answer to my question (I can't found).
When I try to provide mock dependency I got error about: Token must be defined
What is this? How I can provide mock dependency? (in provided component exist some next level dependency - from http and config, so I can create it in real (because he failed withoud his own dependencies...And I think I must to mock this dependency).
There is my test
import {provide} from 'angular2/core';
import {setBaseTestProviders} from 'angular2/testing';
import {
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
} from 'angular2/platform/testing/browser';
import { beforeEach,beforeEachProviders,describe,expect,
provide,
it,
inject,
injectAsync,
TestComponentBuilder,
AsyncTestCompleter} from 'angular2/testing';
import {HTTPPatientsListService} from '../../shared/http_services/http_patients_list.service';
import {PatientsListComponent} from './patients_list.component';
class MockClass {}
describe('Patients list Tests', () => {
beforeEachProviders(() => [
provide(HTTPPatientsListService, {useClass: MockClass})
]);
it('Should defined recentPatientData ', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(PatientsListComponent).then((componentFixture: ComponentFixture) => {
const element = componentFixture.nativeElement;
componentFixture.detectChanges();
});
}));
});
There part of my component (only part. it is work correctly, buy he too long)
#Component({
selector: 'cgm_patients_list',
templateUrl: `${MODULE_PATH}/patients_list.component.html`,
styleUrls: [`..${MODULE_PATH}/patients_list.component.css`],
pipes: [SearchPipe],
providers: [HTTPPatientsListService],
directives: [PatientsListDetailComponent]
})
export class PatientsListComponent implements OnInit {
public recentPatientData;
private pipedPatientsData;
constructor(
private patientsListService: HTTPPatientsListService) {
}
thanks for any help...
P.S. Error is:
Chrome 49.0.2623 (Windows 7 0.0.0) Patients list Tests Should defined recentPatientData FAILED
Failed: Token must be defined!
Error: Token must be defined!
at new BaseException (D:/nucleous/client/src/www/node_modules/angular2/bundles/angular2.dev.js:7521:21)
You need to override the providers of the test component
return tcb
.overrideProviders(PatientsListComponent, [provide(HTTPPatientsListService, {useClass: MockClass})])
.createAsync(PatientsListComponent)
.then((componentFixture: ComponentFixture) => {
See also https://angular.io/docs/ts/latest/api/testing/TestComponentBuilder-class.html
Related
I am trying to implement unit tests for an Angular 2 app. But I can't get it it to work.
As test runner mocha is used and executed like this:
mocha -r ts-node/register -t 10000 ./**/*.unit.ts
Consider the following test file where I define two test cases which basically should do the same thing, but neither one is working.
shared.service.unit.ts
import { TestBed, async, inject } from '#angular/core/testing';
import { SharedService } from './shared.service';
import * as Chai from 'chai';
import 'mocha';
const expect = Chai.expect;
describe('SharedService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SharedService],
providers: [SharedService]
});
});
it('should be an object',
inject([SharedService], (service: SharedService) => {
expect(service).to.be.an('object');
})
);
});
describe('SharedService without the TestBed', () => {
let service: SharedService;
beforeEach(() => {
service = new SharedService();
});
it('should be an object', () => {
expect(service).to.be.an('object');
});
});
The first one 'SharedService' uses the Angular Testing Utility. Running it gives:
ReferenceError: Zone is not defined
The second one 'SharedService without TestBed'does not use any Angular code (similar to this example from Angular 2 Testing guide). Running it gives:
TypeError: Reflect.getMetadata is not a function
After adding these lines to the test file:
import 'core-js/es6';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
Both test cases give the same error (from zone.js\dist\zone.js):
TypeError: Cannot read property 'prototype' of undefined
What am I doing wrong?
Got it, just needed to import 'core-js/es7/reflect':
import 'core-js/es7/reflect';
import 'mocha';
import * as Chai from 'chai';
let expect = Chai.expect;
import { SharedService } from './shared.service';
describe('SharedService', () => {
let service: SharedService;
beforeEach(() => {
service = new SharedService();
})
it('should be an object', () => {
expect(service).to.be.an('object');
})
});
You need to load all that stuff - angular, ngzone, metadata, es shims, etc. - statically in the mocha's - or systemjs or whatever you use for setting this stuff up - configuration.
I'm just now learning unit testing in general, but I'm simultaneously learning angular2. Angular CLI created my test scaffolds and I've configured the testing module with the neccessary component and directive. I'm trying to provide a stub service similar to the one decribed in the DOCS, but I'm getting the following error:
Error: Cannot create the component FoodListComponent as it was not imported
into the testing module
This is odd because the should create test passed when with the FoodListComponent before I added the service into the providers: []. My service works fine in the application, but I can provide it too if you need to see it.
Basically I'd like to know where in this testing set up I need to put the foodDataServiceStub, whether I'm adding the stub service to the TestBed object correctly, and where I need to inject the service via foodDataService = TestBed.get(FoodDataService);?
I'd be thankful for an explanation as well.
food-list.component.spec.ts
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement, NO_ERRORS_SCHEMA } from '#angular/core';
import { TickerDirective } from '../../directives/ticker.directive';
import { FoodListComponent } from './food-list.component';
describe('FoodListComponent', () => {
let component: FoodListComponent;
let fixture: ComponentFixture<FoodListComponent>;
let foodDataServiceStub = {
name: 'test food name',
img: '',
description: 'test food description'
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FoodListComponent, TickerDirective ],
providers: [ { provide: FoodDataService, useValue: foodDataServiceStub } ], /* This line gives me the error */
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FoodListComponent);
component = fixture.componentInstance;
// foodDataService = TestBed.get(FoodDataService);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Since angular 2.0.0-rc.4 release (change log), both beforeEachProviders and provide are deprecated. My question is how can I inject my mocked service inside my component.
Here is my beforeEach function which creates a ComponentFixture with overrided providers:
beforeEach(async(inject([TestComponentBuilder], (tcb)=> {
builder = tcb;
})));
beforeEach(async(inject([], ()=> {
return builder
.overrideProviders(MenubarComponent,[provide(MenubarService, {useClass:MenubarServiceMock})])
.createAsync(MenubarTestComponent)
.then((_fixture:ComponentFixture<any>)=> {
fixture = _fixture;
})
})));
This approach works fine but as I said provide is deprecated.
What is the right way to do this without using provide()?
Edit
Based on Günter's answer I changed the line:
.overrideProviders(MenubarComponent,[provide(MenubarService, {useClass:MenubarServiceMock})])
to:
.overrideProviders(MenubarComponent,[{provide:MenubarService, useClass:MenubarServiceMock}])
and it works!
From the changelog
import {addProviders, inject} from '#angular/core/testing';
describe('my code', () => {
beforeEach(() => {
addProviders([MyService]);
});
it('does stuff', inject([MyService], (service) => {
// actual test
});
});
Instead of provide() use
{provide: MenubarService, useClass:MenubarServiceMock}
I'm having trouble unit testing with NavController.
I'm stuck at this error:
Cannot resolve all parameters for 'NavController'(?, ?, ?, ?, ?, ?, ?, ?). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'NavController' is decorated with Injectable.
I tried everything I found on the net, like using '#Inject', and nothing seems to work.
Here is the code:
Component
import {Page, MenuController, NavController} from 'ionic-angular';
import {SignupPage} from '../signup/signup';
#Page({
templateUrl: 'build/pages/welcome/welcome.html'
})
export class WelcomePage {
// Variables
constructor(private menu: MenuController, private nav: NavController) {
this.menu.enable(false);
}
goToSignupPage() {
this.nav.push(SignupPage)
}
}
Unit test
import {beforeEachProviders, it, describe, expect, inject} from '#angular/core/testing';
import {MenuController, NavController} from 'ionic-angular';
import {WelcomePage} from './welcome';
describe('WelcomePage', () => {
beforeEachProviders(() => [WelcomePage, MenuController, NavController]);
it('should have the menu disabled on instatiation', inject([WelcomePage], (welcomePage) => {
// Expectations
expect(welcomePage.menu.isEnabled()).toBeFalsy();
}));
});
Any idea whats wrong?
UPDATE:
Thanks for the replies, guys.
It really helped me to understand how to do it.
I didn't use sinon, but I was able to test if the push was called
using the spyOn from Jasmine.
For that, I did a subtle change to the provide part:
beforeEachProviders(() => [WelcomePage, MenuController,
{ provide: NavController, useValue: {push: NavController.prototype.push} }]);
(Probably it would be nice to serve the NavController.prototype directly to have access to all the other properties.)
And then tested like this:
it('should go to signup page when calling goToSignupPage()',
inject([WelcomePage], (welcomePage) => {
// Spies
spyOn(welcomePage.nav, 'push').and.stub();
// Call
welcomePage.goToSignupPage();
// Expectations
expect(welcomePage.nav.push).toHaveBeenCalledWith(SignupPage);
}));
Try this on your Unit Test class:
beforeEachProviders(() => [WelcomePage, MenuController, provide(NavController, { useValue: WelcomePage })]);
and then:
import {provide} from '#angular/core';
As GoldBones, the problem is that the NavController that has been imported is a class not a provider, so you need to define your own provider. The provide(...) syntax is deprecated, but the final code is pretty similar to the old syntax:
beforeEachProviders(() => [WelcomePage, MenuController, {provide: NavController, useValue: {} }]);
I've used an empty object above, but as WelcomePage uses the push method, you will need to stub this out with something like:
let stubNavController = {push: (page) => {}};
beforeEachProviders(() => [WelcomePage, MenuController, {provide: NavController, useValue: stubNavController }]);
Using a spying library like Sinon could be useful here if you want to test that the method was called.
I have a simple angular2 component as defined below. And I'm looking to create a unit testing with karma, jasmine to run through this component.
#Component({
selector: 'property',
template: require('./property.component.html'),
directives: [Panel],
providers: [ConfigService]});
export class PropertyComponent {
config:any;
constructor(config:ConfigService) {
this.config = config.getConfig();
}
}
This is my testing spec file.
describe('property component', () => {
it('should have property page title', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(PropertyComponent).then((fixture) => {
let propertyComp = fixture.componentInstance,
element = fixture.nativeElement;
expect(element.querySelector('h1').innerText).toBe('property page');
});
}));
})
However I got a list of weird errors... I'm guessing this is due to the ConfigService Provider in the PropertyComponent, because when I removed the provider dependency, it went through.
Does anyone know how to deal with the dependency Providers?
Thanks!
errors:
_instantiateProvider#angular2-seed/config/spec-bundle.js:23435:38
_new#angular2-seed/config/spec-bundle.js:23424:42
getObjByKeyId#angular2-seed/config/spec-bundle.js:22937:38
_getByKeyDefault#angular2-seed/config/spec-bundle.js:23641:51
_getByKey#angular2-seed/config/spec-bundle.js:23587:42
_getByDependency#angular2-seed/config/spec-bundle.js:23573:35
_instantiate#angular2-seed/config/spec-bundle.js:23463:53
_instantiateProvider#angular2-seed/config/spec-bundle.js:23435:38
_new#angular2-seed/config/spec-bundle.js:23424:42
instantiateProvider#angular2-seed/config/spec-bundle.js:22924:35
init#angular2-seed/config/spec-bundle.js:34694:44
AppElement#angular2-seed/config/spec-bundle.js:34371:33
viewFactory_HostPropertyComponent0
createRootHostView#angular2-seed/config/spec-bundle.js:35741:48
You need to use beforeEachProviders in this case:
import {beforeEachProviders, describe, it, expect} from 'angular2/testing';
//...other imports...
describe('property component', () => {
beforeEachProviders(()=> [
ConfigService, //if you don't need to mock
provide(ConfigService, {useClass:MockConfigService}) // more typical
]);
it('should have property page title', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(PropertyComponent).then((fixture) => {
//expectations...
});
}));
})
Note that you need to import angular's patched describe, it, expect functions along with beforeEachProvidersfrom angular2/testing. I emphasize this because it's easy to forget to do that, and it results in failures with rather unintuitive messages.