1

I am creating unit tests for a Login page (ionic 3 angular 4 j). My application is built on ionic unit test example

I am trying to mock a provider that is authenticating my user.

class UsersProviderMock {
    public login():any {
        console.warn("MOCK TEST")
    }
}

My Login page code is:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { DashboardPage } from '../dashboard/dashboard'
import { Platform } from 'ionic-angular';
import {UsersProvider} from '../../providers/users/users'

@IonicPage()
@Component({
  selector: 'page-login',
  templateUrl: 'login.html',
  providers:[UsersProvider]
})
export class LoginPage {

  email: string = "";
  password: string = "";

  constructor(public navCtrl: NavController,
    public navParams: NavParams,
    private platform: Platform,
  private userprovider:UsersProvider) { }

  ionViewDidLoad() {
  }

  login() {

    console.warn("LOGIN")    
    this.userprovider.login()

  }

}

What i am trying to achieve in my unit test is to test that the login method of the provider is called.

My unit test looks like:

describe('Login', () => {
    let inputs: DebugElement[];
    let comp: LoginPage;
    let fixture: ComponentFixture<LoginPage>;
    var mock;

    beforeEach(async(() => {

      TestBed.configureTestingModule({
        declarations: [LoginPage],
        imports: [
            HttpModule,
            IonicModule.forRoot(LoginPage)
        ],
        providers: [
            UsersProvider,
            { provide: Apollo, useClass: ApolloMock },
            NavController,
            { provide: ToastController, useClass: () => ToastControllerMock.instance() },
            { provide: LoadingController, useClass: () => LoadingControllerMock.instance() },
            { provide: NavParams, useClass: NavParamsMock },
            { provide: Platform, useClass: PlatformMock },
            { provide: StatusBar, useClass: StatusBarMock },
            { provide: SplashScreen, useClass: SplashScreenMock },
        ]
        }).overrideComponent(LoginPage, {
        set: {
            providers: [
                { provide: UsersProvider, useClass: UsersProviderMock }
            ]
        }
    })



        fixture = TestBed.createComponent(LoginPage);
        mock=TestBed.get(UsersProvider)
        comp = fixture.componentInstance;
    }))

    it('A Test',()=>{
        spyOn(mock,'login')
        comp.login()
        expect(mock.login).toHaveBeenCalled()
    })

});

What I cannot understand is that the login method of my mock class is called. I have the warn message in the console (WARN LOG: 'MOCK TEST').

But my test A Test fails with the message :

Expected spy test to have been called.
gpasse
  • 4,380
  • 7
  • 44
  • 75

1 Answers1

3

MOCK TEST in the console means that a spy wasn't called and original UsersProviderMock method was called instead. A better way to detect faulty behaviour is to throw errors in methods that aren't supposed to be called:

class UsersProviderMock {
    public login():any {
        throw new Error('should not be called');
    }
}

mock=TestBed.get(UsersProvider) gets provider instance from root injector, while a component gets an instance from its own injector - which is unspied UsersProviderMock. mock is provider instance that is unused anywhere but a test itself. This scenario is possible because UsersProvider was provided to test bed, while it's not needed:

    providers: [
        UsersProvider,
        ...

One way to spy on component instance is is to spy on prototype method (as explained in this answer):

    spyOn(UsersProviderMock.prototype,'login')
    comp.login()
    expect(UsersProviderMock.prototype.login).toHaveBeenCalled()

Another way is to avoid UsersProviderMock class at all because it's redundant here:

    set: {
        providers: [
            { provide: UsersProvider, useValue: jasmine.createSpyObj('users', ['login']) }
        ]
    }

    ...
    comp.login()
    expect(UsersProviderMock.prototype.login).toHaveBeenCalled()
Estus Flask
  • 206,104
  • 70
  • 425
  • 565