Problems with NavController when unit testing an Ionic 2 app - unit-testing

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.

Related

Working Example of an Unit Test of a Ionic Page with lazy load

For some time I am trying to rewrite my 'broken' unit tests since lazy loading with IonicPageModule, without any luck.
Anyone who has some pointers or working examples is greatly appreciated.
Should no working example exist (which I doubt), I will help create one with the feedback gathered on this page.
Any help is welcome.
Thanks to the help of RomainFallet I've found (and recreated) a working ionic 3 Unit Test with lazy loading on his github (see 2.7), which I also share here for ease.
/* Import Ionic & Angular core elements */
import { async, TestBed } from '#angular/core/testing';
import { IonicModule } from 'ionic-angular';
/* Import modules */
import { HomePageModule } from './home.module';
/* Import components */
import { AppComponent } from '../../app/app.component';
/* Import pages */
import { HomePage } from './home';
describe('HomePage', () => {
let fixture;
let component;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [],
imports: [
IonicModule.forRoot(AppComponent),
HomePageModule
],
providers: [
]
})
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomePage);
component = fixture.componentInstance;
});
it ('should create a valid instance of HomePage', () => {
expect(component instanceof HomePage).toBe(true);
});
});
Hope it also helps others finding their way.

How to add service to Angular2 test (CLI config, not as in docs)

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();
});
});

How to write an HTTP mock unit test in Angular 2 final release?

I have moved from RC4 to final release (2.1.0) and I am refactoring my unit tests to conform to 2.1.0 syntax. This is easy except for HTTP mocking.
I cannot find any examples for how to mock HTTP requests in 2.1.0
Here is an RC4 HTTP unit test. How would I re-write this in final release 2.1.0?
it('ngOnInit()',
async(inject([TestComponentBuilder, XHRBackend], (tcb:TestComponentBuilder, mockBackend:MockBackend) => {
tcb.createAsync(Route1ListComponent).then((fix:ComponentFixture<Route1ListComponent>) => {
// THIS BLOCK OF CODE I NEED HELP TO RE-WRITE TO 2.1.0
mockBackend.connections.subscribe(
(connection:MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: persons
}
)));
});
// THIS BLOCK OF CODE WILL NOT CHANGE
let instance = fix.componentInstance;
instance.ngOnInit();
expect(instance.persons.length).toBe(3);
});
})));
NOTE: Do NOT provide RC code please. Thanks
First thing you need to do is configure the TestBed. There's no more TestComponentBuilder. With the TestBed, it's like configuring an #NgModule from scratch, just for the test environment. This means you will add the component under test to the declarations, add any providers to the provider, and any imports to the imports.
To configure the mock backend for Http provider, you would just create the Http from the MockBackend.
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpModule ],
declarations: [ RouteListComponent ],
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) => {
return new Http(backend, options);
},
deps: [ MockBackend, BaseRequestOptions ]
}
]
})
})
That should be it for the configuration, assuming you don't need any other providers or imports I don't know about.
For the test you'll first want to make it an async test, as you'll be doing asynchronous operations in the test. This hasn't changed from RC, you just use async. If the component uses templateUrl (and you're not using Webpack), then you will need to call TestBed.compileComponents(), otherwise no need to. After that you can create the component with TestBed.createComponent
let fixture: ComponentFixture<RouteListComponent>;
let component: RouteListComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({ ... })
.compileComponents().then(() => {
fixture = TestBed.createComponent(RouteListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('...', async(inject([MockBackend], (backend: MockBackend) => {
})))
Pretty much all the things above related to testing can be imported from #angular/core/testing. Your use of the MockBackend would still be the same.
Another note, you don't need to call the component.ngOnInit. That is called by the framework when you call fixture.detectChanges()
See Also:
Testing documentation for more complete examination of testing support.
Many thanks to #peeskillet for helping me reach my answer ..
import {APP_BASE_HREF} from '#angular/common';
import {async, ComponentFixture, TestBed} from '#angular/core/testing';
import {By} from '#angular/platform-browser';
import {AppModule} from '../../../app.module';
import {persons} from '../../../data/persons';
import {Route1ListComponent} from './route1-list.component';
// HTTP mocking imports
import {BaseRequestOptions, Http, Response, ResponseOptions} from '#angular/http';
import {MockBackend, MockConnection} from '#angular/http/testing';
describe('route1-list.component.ts', () => {
let fix: ComponentFixture<Route1ListComponent>;
let instance: Route1ListComponent;
let injector: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AppModule],
providers: [{provide: APP_BASE_HREF, useValue: '/'},
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (pBackend: MockBackend, pOptions: BaseRequestOptions) => {
return new Http(pBackend, pOptions);
},
deps: [MockBackend, BaseRequestOptions]
}]
}).compileComponents()
.then(() => {
fix = TestBed.createComponent(Route1ListComponent);
instance = fix.componentInstance;
injector = fix.debugElement.injector;
});
}));
it('should instantiate component', () => {
expect(instance).toEqual(jasmine.any(Route1ListComponent));
});
it('should have expected text', () => {
let el = fix.debugElement.query(By.css('section.route1-list')).nativeElement;
expect(el.textContent).toMatch(/route 1 list view/i, 'should have expected text');
});
it('ngOnInit()', async(() => {
let backend = injector.get(MockBackend);
backend.connections.subscribe(
(connection: MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: persons
}
)));
});
fix.detectChanges(); // Calls instance.ngOnInit()
expect(instance.persons.length).toBe(3);
}));
it('ngOnInit() failure', async(() => {
let backend = injector.get(MockBackend);
backend.connections.subscribe(
(connection: MockConnection) => {
connection.mockError(new Error('error'));
});
fix.detectChanges(); // Calls instance.ngOnInit()
expect(instance.persons).toBeUndefined();
}));
});
Notice that, at the time of writing, the Angular2 docs at ..
https://angular.io/docs/ts/latest/api/http/testing/index/MockBackend-class.html
https://angular.io/docs/ts/latest/api/http/testing/index/MockConnection-class.html
seem to be wrong.
When I use Injector.resolveAndCreate as detailed in the docs I get the error:
Property 'resolveAndCreate' does not exist on type 'typeof Injector'.
To fix it I had to base my answer on the answer provided by #peeskillet

angular2 rc.4: How to inject mocked services (provide() is deprecated)

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}

Issue with angular2 tesing DI

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