ionic-team / ionic-unit-testing-example

Example of adding unit testing in your Ionic 2.x or greater apps with Karma and Jasmine
Other
374 stars 145 forks source link

Can't resolve all parameters for navParams? #54

Closed jainAdijain closed 7 years ago

jainAdijain commented 7 years ago

After cloning the Repo of ionic-unit-testing-example I wrote test cases to initialise Page2, So it failed by showing an error thatCan't resolve all parameters for navParams?

import { async, TestBed, ComponentFixture } from "@angular/core/testing";
import { Page2 } from "./page2";
import { IonicModule, NavController } from "ionic-angular/index";
import { DebugElement } from "@angular/core";
import { By } from "@angular/platform-browser";
import { NavParams } from "ionic-angular/navigation/nav-params";

describe('Page2 should be tested', () => {
    let comp: Page2;
    let fixture: ComponentFixture<Page2>;
    let de : DebugElement;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [Page2],
            imports: [IonicModule.forRoot(Page2)],
            providers: [
                NavController,
                NavParams
            ]
        })
    }));
        beforeEach( ()=>{
            fixture = TestBed.createComponent(Page2);
                comp  = fixture.componentInstance;
                de   = fixture.debugElement.query(By.css('h4'));
        });

        it('against component initialisation', ()=>{
            expect(fixture).toBeTruthy();
            expect(comp).toBeDefined();
        })

})

Please suggest?

datencia commented 7 years ago

Hi, I don't know. Looking the NavParams class, I see that the constructor has an argument of type any. I think the DI cannot resolve it because of it.

Anyway, you can (and I think you should) mock NavParams (and all external dependencies you use). With this in mind, just replace the NavParams provider as follow:

...
let navParamsStub = {
    get: (param: string): any => undefined
};

beforeEach(async(() => {
    TestBed.configureTestingModule({
        declarations: [Page2],
        imports: [IonicModule.forRoot(Page2)],
        providers: [
            NavController,
            {provide: NavParams, useValue: navParamsStub}
        ]
    })
}));
...

Tests should now pass.

The only thing that would remain is to mock the rest of dependencies.

jainAdijain commented 7 years ago

Wow! Thanks, datencia, It means for any real service or function we create mocks? But I have still not got how to create a mock for any thing? Here in the below code, I want to test: 1.) The NavController Service is working fine or not, (here I need the NavController mock!!!) 2.) I want to add a spy on my click() function.

How are these both achievable?

For example:

home.html

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  The world is your oyster.
  <p>
    If you get lost, the <a href="http://ionicframework.com/docs/v2">docs</a> will be your guide.
  </p>
  <h4>Testing</h4>

  <button ion-button color="primary" clear large (click)="onClick()">Click</button>
</ion-content>

home.ts

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Page1 } from "../page1/page1";

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  constructor(public navCtrl: NavController) {

  }

  onClick(){
    this.navCtrl.push(Page1);
  }

}

home.spec.ts

import { async, TestBed, ComponentFixture } from "@angular/core/testing";
import { HomePage } from "./home";
import { IonicModule, NavController } from "ionic-angular";
import { DebugElement } from "@angular/core";
import { By } from "@angular/platform-browser";

describe('HomePage', () => {

    let fixture: ComponentFixture<HomePage>;
    let comp: HomePage;
    let de: DebugElement;
    let el: HTMLElement;
    let NavCtrlSpy : any;
    let pushSpy: any;

    beforeEach(async(() => {
        TestBed.configureTestingModule({

            declarations: [HomePage],
            imports: [
                IonicModule.forRoot(HomePage)
            ],
            providers: [
               NavController
            ]

        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(HomePage);
        comp = fixture.componentInstance;
        de = fixture.debugElement.query(By.css('h4'));
        el = de.nativeElement;
        navCtrlSpy = fixture.componentInstance.NavController;//I need here mock for NavController
        pushSpy = fixture.componentInstance.NavController.push();
    });

    it('initializes', () => {
        expect(fixture).toBeTruthy();
        expect(comp).toBeDefined();
    });

    it('checks for the h4 element in the DOM', () => {
        fixture.detectChanges();
        expect(el.textContent).toContain('Testing');
    });

        it('looks for onClick()', ()=>{
        expect(comp.onClick).toBeTruthy();

    });

    //here I will write a test case for Navcontroller.

});
datencia commented 7 years ago

Most of the time you will mock all the dependencies. A component under test doesn't have to be injected with real services.

The purpose of a spec is to test the component, not the services involved.

Imagine a component that uses a service to fetch data from a rest API using HTTP, You want to test your component without collateral effects and always with the same data. E.g. What if the network is down or the data changes between one test execution and another? We must ensure that the tests always run under the same premises without external "influences" that can modify their behavior.

In your case, you don't have to test if the NavController works or not, You must make an act of faith and assume that NavController will work as expected.

So in your example, you could create a stub for the NavController, then get a reference to it from the test root injector and then spy the push method to check if it is really called when the user clicks the button. E.g:

import { async, TestBed, ComponentFixture, inject } from "@angular/core/testing";
import { NO_ERRORS_SCHEMA } from "@angular/core";
import { By } from "@angular/platform-browser";
import { NavController } from "ionic-angular";

import { HomePage } from "./home";
import { Page1 } from "../page1/page1";

describe('HomePage', () => {

    let fixture: ComponentFixture<HomePage>;
    let comp: HomePage;

    let navControllerService: NavController;

    let navControllerStub = {
        push: (page: any): Promise<any> => Promise.resolve('done')
    };

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            schemas: [ NO_ERRORS_SCHEMA ],
            declarations: [ HomePage ],
            providers: [
                {provide: NavController, useValue: navControllerStub}
            ]

        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(HomePage);
        comp = fixture.componentInstance;
    });

    beforeEach(inject([NavController], (service: NavController) => {
        navControllerService = service;
    }));

    // here I will write a test case for Navcontroller.
    it('should navigate to the page1 when the user clicks the button', () => {
        spyOn(navControllerService, 'push');
        const button: HTMLButtonElement = fixture.debugElement.query(By.css('button')).nativeElement;
        button.click();
        expect(navControllerService.push).toHaveBeenCalledWith(Page1);
    });

});
jainAdijain commented 7 years ago

Perfectly understood the above example.Thank you very much for explaining this very useful knowledge with me. The main problem now I face is that, how should I create the stub, similar that you have created in the above example

 let navControllerStub = {
        push: (page: any): Promise<any> => Promise.resolve('done')
    };

And secondly, For Example I have a service class which contains the following code, service.ts

import { Injectable } from "@angular/core";
import {  Http } from '@angular/http';
@Injectable()
export class Service {

    public http: string;

    constructor(private http: Http) { }

    getGoogle():any {
        return this.http.get('https://firebaseurl');
    }
}

Now how to create a stub for this service class, these are the challenges I am facing. If you can share some steps to create any sort of stub as you created It would add a very useful part to my knowledge.

Thank you :-)

datencia commented 7 years ago

Hi, sorry but I think this is not the right place to ask about generic unit testing topics. You can find a lot of information on the Angular's testing guide and you can ask our friends Google & StackOverflow as well ;)

jainAdijain commented 7 years ago

Okay Thank you datencia, Actually, I am barred from asking questions on SO, so I don't have any other platform.But it's okay will find it digging hard anywhere else.Anyways thank you for sharing knowledge :-)