Ionic 2 ViewController unit testing - unit-testing

mIve got the following unit tests that tests a Component I have written in Ionic 2. The unit tests gives an error from one of the Ionic libraries, I assume that I am not mocking it out properly or as such
import { ComponentFixture, async } from '#angular/core/testing';
import { TestUtils } from '../../test';
import {} from 'jasmine';
import { LocationSearchModal } from './LocationSearchModal';
import { LocationService } from '../../services/LocationService';
import { PouchDbService } from '../../services/common/PouchDbService';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { TestBed } from '#angular/core/testing';
import { App, MenuController, NavController, Platform, Config, Keyboard, Form, IonicModule, ViewController, GestureController, NavParams } from 'ionic-angular';
import { ConfigMock } from '../../mocks';
import { TranslateModule } from 'ng2-translate';
import { LoadingController } from 'ionic-angular';
let fixture: ComponentFixture<LocationSearchModal> = null;
let instance: any = null;
describe('LocationSearchModal', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
LocationSearchModal
],
providers: [
App, Platform, Form, Keyboard, MenuController, NavController, GestureController, LocationService, LoadingController,
{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
{ provide: NavParams, useClass: class { NavParams = jasmine.createSpy("navParams"); } },
{ provide: PouchDbService, useClass: class { PouchDbService = jasmine.createSpy("pouchDbService"); } },
{provide: Config, useClass: ConfigMock}
],
imports: [
FormsModule,
IonicModule,
ReactiveFormsModule,
TranslateModule.forRoot(),
],
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(LocationSearchModal);
instance = fixture.debugElement.componentInstance;
fixture.autoDetectChanges(true);
});
}));
afterEach(() => {
fixture.destroy();
});
it('loads', () => {
expect(fixture).not.toBeNull();
expect(instance).not.toBeNull();
})
})
This is the relevant excerpt which uses the ViewController from the component that is being tested.
this.locationService.getLocationById(this.selectedLocation)
.subscribe((location: any) => {
this.viewController.dismiss(location.doc)
});
The test fails and I get the following stack trace
Chrome 53.0.2785 (Linux 0.0.0)
TypeError: viewCtrl._setHeader is not a function
at new Header (webpack:///home/milinda/workspaces/eclipse/inspection/addedinspection/Inspection-Upgrade/~/ionic-angular/components/toolbar/toolbar.js:14:0 <- src/test.ts:11833:30)
at new Wrapper_Header (/IonicModule/Header/wrapper.ngfactory.js:7:18)
This is related to the ViewController line which I have created a jasmine spy for
{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
After having a look at the code base I found the _setHeader method in here
https://github.com/driftyco/ionic/blob/6b3e2ed447340cdd35c328c96aa7cfa5f34eb214/src/navigation/view-controller.ts#L364
I also tried writing a custom provider but got the same error as well. Any idea on what is the correct method of testing the ViewController.
Additionally sometimes after resolving the ViewController issue an issue may occur from NavParams perhaps

Building on Marky Sparky's answer. As of ionic 3+:
export class ViewControllerMock{
readReady = {
subscribe(){
}
};
writeReady = {
subscribe(){
}
};
dismiss(){
console.log('View Controller Dismiss Called');
}
_setHeader(){
}
_setNavbar(){
}
_setIONContent(){
}
_setIONContentRef(){
}
}
Working on version:
Cordova CLI: 6.5.0
Ionic Framework Version: 3.0.1
Ionic CLI Version: 3.0.0-beta7
ios-deploy version: 1.9.1
ios-sim version: Not installed
OS: macOS Sierra
Node Version: v7.8.0
Xcode version: Xcode 8.3.2 Build version 8E2002

I had the same issue when referring to ViewController in unit tests. I just solved it.
Create a mock like this
class ViewControllerMock {
public _setHeader(): any {
return {}
};
public _setIONContent(): any {
return {}
};
public _setIONContentRef(): any {
return {}
};
}
Then add it to your providers in the call to TestBed.configureTestingModule like this:
TestBed.configureTestingModule({
declarations: [
...components,
OrdinalPipe,
IgnoreNulls
],
providers: [
NavController,
ChartsService, FundsService, Utils, BlogService
, Payment, PlanHelper, Storage, PalIdle, SimpleExpiry, ContentService, PlansService,
App, Platform, Form, Keyboard, MenuController,
{ provide: ModalController, useClass: ModalControllerMock },
{ provide: ViewController, useClass: ViewControllerMock },
{ provide: Config, useClass: ConfigMock }
],
imports: [
FormsModule,
IonicModule,
ReactiveFormsModule,
],
})
This worked for me when I had the viewCtrl._setHeader is not a function error earlier today. Hope it helps.

Accepted answer did not work for ionic version 3.9.2, however following fixed the issue:
export class ViewControllerMock {
public readReady: any = {
emit(): void {
},
subscribe(): any {
}
};
public writeReady: any = {
emit(): void {
},
subscribe(): any {
}
};
public contentRef(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public didEnter(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public didLeave(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public onDidDismiss(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public onWillDismiss(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public willEnter(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public willLeave(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public willUnload(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public dismiss(): any {
return true;
}
public enableBack(): any {
return true;
}
public getContent(): any {
return true;
}
public hasNavbar(): any {
return true;
}
public index(): any {
return true;
}
public isFirst(): any {
return true;
}
public isLast(): any {
return true;
}
public pageRef(): any {
return true;
}
public setBackButtonText(): any {
return true;
}
public showBackButton(): any {
return true;
}
public _setHeader(): any {
return true;
}
public _setIONContent(): any {
return true;
}
public _setIONContentRef(): any {
return true;
}
public _setNavbar(): any {
return true;
}
public _setContent(): any {
return true;
}
public _setContentRef(): any {
return true;
}
public _setFooter(): any {
return true;
}
}
Ionic Info
cli packages:
#ionic/cli-plugin-proxy : 1.5.6
#ionic/cli-utils : 1.14.0
ionic (Ionic CLI) : 3.14.0
local packages:
#ionic/app-scripts : 3.1.0
Ionic Framework : ionic-angular 3.9.2

In case someone doesn't notice the comments on James Macmillan's answer:
#eesdil wrote:
From beenotung, there is built in mocks in ionic also: import
{mockApp, mockConfig, mockPlatform, mockView} from
"ionic-angular/util/mock-providers"; and use it like {provide:
ViewController, useValue: mockView()}, – Sep 26 '17 at 9:40
This is the solution that worked for me.

1. Create Jasmine spy of ViewController.
let viewCtrlSpy = jasmine.createSpyObj('ViewController',
['data', 'readReady', 'writeReady', 'dismiss', '_setHeader', '_setNavbar', '_setIONContent', '_setIONContentRef']);
2. Use viewCtrlSpy spy in providers array as follow:
providers: [
.......
{
provide: ViewController,
useValue: viewCtrlSpy
}
..........
Spying is more efficient in this case than mocking.

add
{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
to provides

Related

Why is the service not injected into the controller?

I'm trying out AdonisJS, but I'm stuck trying to get a service injected into a controller. No matter what I try, the hs constructor argument in VersionsController remains undefined.
I've also experimented with annotating the VersionController constructor with #inject(['#ioc:Service/HashService']), but with no luck. I'm not sure though if #inject is the right way for Adonis.js v5.
How do I properly inject a service class into a controller?
/providers/AppProvider.ts
import { ApplicationContract } from '#ioc:Adonis/Core/Application'
import HashService from 'App/Services/HashService';
export default class AppProvider {
protected app: ApplicationContract;
constructor(app: ApplicationContract) {
this.app = app;
}
public register() {
this.app.container.singleton('#ioc:Service/HashService', () => {
return new HashService();
});
}
public async boot() {
// IoC container is ready
}
public async ready() {
// App is ready
}
public async shutdown() {
// Cleanup, since app is going down
}
}
/app/Services/HashService.ts
'use strict'
export default class HashService {
async test() {}
}
module.exports = HashService;
app/Controllers/Http/VersionsController.ts
import { HttpContextContract } from '#ioc:Adonis/Core/HttpContext'
import { HashService } from '#ioc:Service/HashService'
export default class VersionsController {
protected hs: HashService
constructor(hs: HashService) {
this.hs = hs;
console.log("hashservice is " + hs);
}
public async get(ctx: HttpContextContract) {
return [
{
id: 1,
title: 'a'
}
]
}
public async put(ctx: HttpContextContract) {
return "PUT";
}
}
.adonisrc.json
{
"typescript": true,
"commands": [
"./commands",
"#adonisjs/core/build/commands/index.js",
"#adonisjs/repl/build/commands"
],
"exceptionHandlerNamespace": "App/Exceptions/Handler",
"aliases": {
"App": "app",
"Config": "config",
"Database": "database",
"Contracts": "contracts"
},
"preloads": [
"./start/routes",
"./start/kernel"
],
"providers": [
"./providers/AppProvider",
"#adonisjs/core"
],
"aceProviders": [
"#adonisjs/repl"
]
}
you can use Adonisjs/fold
simply use the #inject decorator in your services;
example;
import {HttpContextContract} from "#ioc:Adonis/Core/HttpContext";
import UserEditValidator from "App/Validators/UserEditValidator";
import UserRepository from "App/Repository/UserRepository";
import {inject} from "#adonisjs/fold";
#inject()
export default class UserService {
constructor(private userRepository: UserRepository) {
}
async update(ctx: HttpContextContract) {
//do your stuff
}
}
and then in your controller;
import {HttpContextContract} from "#ioc:Adonis/Core/HttpContext";
import UserService from "App/Service/UserService";
import {inject} from "#adonisjs/fold";
#inject()
export default class UserController {
constructor(private userService: UserService) {
}
async edit({ view }){
return view.render('user/edit', {title: 'Edit Profile'})
}
async update(ctx : HttpContextContract){
await this.userService.update(ctx);
return ctx.response.redirect().back()
}
}

Angular 4/CLI Unit test issue How to test a method of component that contains input object variable

Could somebody guide me how can I implement a unit test for the component below? I'm going to test the next() method of this component.When I implement a unit test for this function I got an error.Actually I'm beginner in unit test so I appreciate if somebody implements a Professional unit test on this sample which be reference for me for other components.
component file:
import { Component, Input, OnInit } from '#angular/core';
import { Client, ClientMetadata } from '../../shared/clients/client.model';
import { ClientService } from '../../shared/clients/client.service';
import { HomeRoutingService } from '../home-routing/home-routing.service';
import { FormValidationService } from "../../shared/form-validation/form-
validation.service";
import { FormBuilder} from '#angular/forms';
#Component({
selector: 'email-form',
templateUrl: './email-form.component.html',
styleUrls: ['./email-form.component.css', '../home.component.css'],
})
export class EmailFormComponent implements OnInit{
#Input() client: Client;
isClickedIncognito: boolean;
isClickedNext: boolean;
constructor(
private clientService: ClientService,
private homeRoutingService: HomeRoutingService,
public fv: FormValidationService
) { }
ngOnInit() {
this.isClickedIncognito = false;
this.isClickedNext = false;
// form is builded in fv service
this.fv.buildFormEmail();
}
next(anonymous: boolean): void {
document.body.style.cursor = 'wait';
this.client.anonymous = anonymous === true ? 1 : 0;
// If client is anonymous go directly to measurement form
if (anonymous) {
this.isClickedIncognito = true;
this.saveClient();
this.homeRoutingService.next(this.constructor.name, { anonymous:
true });
}
// Check if client exists in DB ; check if has password ;
else {
this.isClickedNext = true;
this.clientService.checkExist(this.client.email)
.then(exists =>
this.handleExist(exists)
);
}
}
saveClient(): void {
let gender = new ClientMetadata('gender', environment.gender);
(this.client.clientMetadatas = this.client.clientMetadatas ?
this.client.clientMetadatas : []).push(gender);
if (this.client.anonymous === 1)
this.client.email = null;
else if (this.client.email === null) { return; }
this.clientService.addClient(this.client)
.then(client => this.client = client);
}
}
spec file :
import { ComponentFixture, TestBed, async } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement, NO_ERRORS_SCHEMA } from '#angular/core';
import { EmailFormComponent } from './email-form.component';
import { ClientService } from '../../shared/clients/client.service';
import { HomeRoutingService } from '../home-routing/home-routing.service';
import { FormValidationService } from "../../shared/form-validation/form-
validation.service";
import { FormBuilder } from '#angular/forms';
import { Client, ClientMetadata } from '../../shared/clients/client.model';
import { TranslateModule, TranslateStaticLoader, TranslatePipe,
TranslateLoader } from 'ng2-translate';
import { Http } from '#angular/http';
export function createTranslateLoader(http: Http) {
return new TranslateStaticLoader(http, '../../assets/i18n/', '.json');
}
describe('EmailFormComponent', () => {
let component: EmailFormComponent;
let fixture: ComponentFixture<EmailFormComponent>;
let de: DebugElement;
let el: HTMLElement;
let formService: FormValidationService;
let clientService: ClientService;
let ClientEl: Client;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [EmailFormComponent],
providers: [FormValidationService,
ClientService,
HomeRoutingService,
FormValidationService,
FormBuilder
// { provide: ClientService, useValue: ClientServiceStub },
// { provide: HomeRoutingService, useValue:
HomeRoutingServiceStub },
// { provide: FormValidationService, useValue: FormValidationServiceStub }
],
schemas: [NO_ERRORS_SCHEMA],
imports: [TranslateModule.forRoot({
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [Http]
})]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EmailFormComponent);
component = fixture.componentInstance;
// formService = fixture.debugElement.injector.get(FormValidationService);
// formService.buildFormEmail();
// clientService = fixture.debugElement.injector.get(ClientService);
// fixture.detectChanges();
});
it('should component works well', async(() => {
const fixture = TestBed.createComponent(EmailFormComponent);
const comp = fixture.debugElement.componentInstance;
expect(comp).toBeTruthy();
}));
it('should be correct', () => {
let anonymous = true;
component.next(anonymous);
//console.log(component.isClickedIncognito);
expect(component.isClickedIncognito).toBe(true);
//expect(true).toBe(true);
});
});
error (when I comment fixture.detectChanges()):
Cannot set property 'anonymous' of undefined
error (when I put fixture.detectChanges()):
Cannot read property 'email' of undefined

Ionic 2: Error testing with Karma/Jasmine/TestBed

I'm new with Ionic2 and I was following this tutorial and a simple test like
describe('Dummy test', () => {
it('should do nothing', () => {
expect(true).toBeTruthy();
expect(1 + 1).toBe(2);
});
});
works fine, but for some reason I keep getting this error when I try to follow the rest of the tutorial.
Component: Root Component
✖ initialises with a root page of LoginPage
Firefox 45.0.0 (Linux 0.0.0)
TypeError: win is undefined in src/test.ts (line 937)
My src/test.ts is the same as the tutorial and it doesn't have any win in it. My app.spec.ts is this
import { TestBed, ComponentFixture, async } from '#angular/core/testing';
import { IonicModule } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { UserData } from '../providers/user-data';
import { LoginPage } from '../pages/login/login';
import { Platform } from 'ionic-angular';
import { MyApp } from './app.component';
import { LoginPage } from '../pages/login/login';
let comp: MyApp;
let fixture: ComponentFixture<MyApp>;
describe('Component: Root Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyApp],
providers: [
StatusBar,
SplashScreen,
UserData,
Platform
],
imports: [
IonicModule.forRoot(MyApp)
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyApp);
comp = fixture.componentInstance;
});
afterEach(() => {
fixture.destroy();
comp = null;
});
it('initialises with a root page of LoginPage', () => {
expect(comp['rootPage']).toBe(LoginPage);
});
});
And my app.component.ts is this
import { Component } from '#angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { MenuSidePage } from '../pages/menu-side/menu-side';
import { LoginPage } from '../pages/login/login';
import { UserData } from '../providers/user-data';
#Component({
template: `<ion-nav #nav [root]="rootPage"></ion-nav>`
})
export class MyApp {
rootPage: any;
constructor(
public platform: Platform,
public statusBar: StatusBar,
public splashScreen: SplashScreen,
private userData: UserData,
) {
platform
.ready()
.then(() => {
//First - check if user is logged
if(this.userData.currentUser) {
this.rootPage = MenuSidePage;
} else {
this.rootPage = LoginPage;
}
statusBar.styleDefault();
splashScreen.hide();
});
}
}
I don't have yet the solution, but you shouldn't use compileComponents() 'cause you are using a template and not a templateUrl like said in this tutorial :
"We need to use compileComponents when we need to asynchronously compile a component, such as one that has an external template (one that is loaded through templateUrl and isn’t inlined with template). This is why the beforeEach block that this code runs in uses an async parameter – it sets up an asynchronous test zone for the compileComponents to run inside."
Hope it's a kind of helping :)
The win() function come from the Plaftorm, you have to mock it as follow :
export class PlatformMock {
public ready(): Promise<string> {
return new Promise((resolve) => {
resolve('READY');
});
}
public getQueryParam() {
return true;
}
public registerBackButtonAction(fn: Function, priority?: number): Function {
return (() => true);
}
public hasFocus(ele: HTMLElement): boolean {
return true;
}
public doc(): HTMLDocument {
return document;
}
public is(): boolean {
return true;
}
public getElementComputedStyle(container: any): any {
return {
paddingLeft: '10',
paddingTop: '10',
paddingRight: '10',
paddingBottom: '10',
};
}
public onResize(callback: any) {
return callback;
}
public registerListener(ele: any, eventName: string, callback: any): Function {
return (() => true);
}
public win(): Window {
return window;
}
public raf(callback: any): number {
return 1;
}
public timeout(callback: any, timer: number): any {
return setTimeout(callback, timer);
}
public cancelTimeout(id: any) {
// do nothing
}
public getActiveElement(): any {
return document['activeElement'];
}
}
Here is the link to see a project for real integration of this mock class.
Hope it helps :)

How to pass Unit test after implementing ngx-bootstrap/datepicker?

I created independent component with ngx-bootstrap/datepicker on angular-cli project.
Everything works fine but Unit test is failing.
But I am new on Unit Testing, and I tried to test but it saying fails.
Here is Travic-CI build log.
https://travis-ci.org/webcat12345/webcat-black-page/builds/221961698
Here is my project version and code.
#angular/cli: 1.0.0
node: 7.6.0
os: linux x64
#angular/cli: 1.0.0
#angular/common: 4.0.2
#angular/compiler: 4.0.2
#angular/compiler-cli: 4.0.2
#angular/core: 4.0.2
#angular/forms: 4.0.2
#angular/http: 4.0.2
#angular/platform-browser: 4.0.2
#angular/platform-browser-dynamic: 4.0.2
#angular/router: 4.0.2
Template
<datepicker class="well well-sm main-calendar" [(ngModel)]="dt" [minDate]="minDate" [showWeeks]="false" [dateDisabled]="dateDisabled"></datepicker>
Component (sorry for posting full code. it is just sample code from ngx-bootstrap demo)
import { Component, OnInit } from '#angular/core';
import * as moment from 'moment';
#Component({
selector: 'app-sidebar-datepicker',
templateUrl: './sidebar-datepicker.component.html',
styleUrls: ['./sidebar-datepicker.component.scss']
})
export class SidebarDatepickerComponent implements OnInit {
public dt: Date = new Date();
public minDate: Date = void 0;
public events: any[];
public tomorrow: Date;
public afterTomorrow: Date;
public dateDisabled: {date: Date, mode: string}[];
public formats: string[] = ['DD-MM-YYYY', 'YYYY/MM/DD', 'DD.MM.YYYY',
'shortDate'];
public format: string = this.formats[0];
public dateOptions: any = {
formatYear: 'YY',
startingDay: 1
};
private opened: boolean = false;
constructor() {
(this.tomorrow = new Date()).setDate(this.tomorrow.getDate() + 1);
(this.afterTomorrow = new Date()).setDate(this.tomorrow.getDate() + 2);
(this.minDate = new Date()).setDate(this.minDate.getDate() - 1000);
(this.dateDisabled = []);
this.events = [
{date: this.tomorrow, status: 'full'},
{date: this.afterTomorrow, status: 'partially'}
];
}
ngOnInit() {
}
public getDate(): number {
return this.dt && this.dt.getTime() || new Date().getTime();
}
public today(): void {
this.dt = new Date();
}
public d20090824(): void {
this.dt = moment('2009-08-24', 'YYYY-MM-DD')
.toDate();
}
public disableTomorrow(): void {
this.dateDisabled = [{date: this.tomorrow, mode: 'day'}];
}
// todo: implement custom class cases
public getDayClass(date: any, mode: string): string {
if (mode === 'day') {
let dayToCheck = new Date(date).setHours(0, 0, 0, 0);
for (let event of this.events) {
let currentDay = new Date(event.date).setHours(0, 0, 0, 0);
if (dayToCheck === currentDay) {
return event.status;
}
}
}
return '';
}
public disabled(date: Date, mode: string): boolean {
return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
}
public open(): void {
this.opened = !this.opened;
}
public clear(): void {
this.dt = void 0;
this.dateDisabled = undefined;
}
public toggleMin(): void {
this.dt = new Date(this.minDate.valueOf());
}
}
Test Code
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { DatepickerModule } from 'ngx-bootstrap/datepicker';
import { SidebarDatepickerComponent } from './sidebar-datepicker.component';
describe('SidebarDatepickerComponent', () => {
let component: SidebarDatepickerComponent;
let fixture: ComponentFixture<SidebarDatepickerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SidebarDatepickerComponent ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [DatepickerModule.forRoot()]
})
.compileComponents();
}));
beforeEach(async() => {
fixture = TestBed.createComponent(SidebarDatepickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Please help me to figure out this issue.
Thank you!
1) Use NO_ERRORS_SCHEMA instead of CUSTOM_ELEMENTS_SCHEMA because:
CUSTOM_ELEMENTS_SCHEMA will allow:
any non-Angular elements with a - in their name,
any properties on elements with a - in their name which is the common rule for custom
but your component without -(datepicker)
NO_ERRORS_SCHEMA will allow any property on any element
TestBed.configureTestingModule({
declarations: [ SidebarDatepickerComponent ],
schemas: [NO_ERRORS_SCHEMA],
imports: [DatepickerModule.forRoot()]
})
2) Another option is importing FormsModule
TestBed.configureTestingModule({
declarations: [ SidebarDatepickerComponent ],
imports: [DatepickerModule.forRoot(), FormsModule]
})

How to mock AngularFire 2 service in unit test?

I'm trying to set up unit tests for a sample Angular 2 app using AngularFire 2 auth, the component is fairly simple:
import { Component } from '#angular/core';
import { AngularFire, AuthProviders } from 'angularfire2';
#Component({
moduleId: module.id,
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css']
})
export class AppComponent {
isLoggedIn: boolean;
constructor(public af: AngularFire) {
this.af.auth.subscribe(auth => {
if (auth) {
this.isLoggedIn = true;
} else {
this.isLoggedIn = false;
}
});
}
loginWithFacebook() {
this.af.auth.login({
provider: AuthProviders.Facebook
});
}
logout() {
this.af.auth.logout();
}
}
All I'm doing is wrapping around the login and logout methods in AngularFire so I was thinking about using a mock to check if the methods were called but I'm not sure where to start, I tried doing the following in my spec file:
import { provide } from '#angular/core';
import { AngularFire } from 'angularfire2';
import {
beforeEach, beforeEachProviders,
describe, xdescribe,
expect, it, xit,
async, inject
} from '#angular/core/testing';
import { AppComponent } from './app.component';
spyOn(AngularFire, 'auth');
beforeEachProviders(() => [
AppComponent,
AngularFire
]);
describe('App Component', () => {
it('should create the app',
inject([AppComponent], (app: AppComponent) => {
expect(app).toBeTruthy();
})
);
it('should log user in',
inject([AppComponent], (app: AppComponent) => {
expect(app.fb.auth.login).toHaveBeenCalled();
})
);
it('should log user out',
inject([AppComponent], (app: AppComponent) => {
expect(app.fb.auth.logout).toHaveBeenCalled();
})
);
});
However I'm not sure how to mock the login and logout methods since they're part of the auth property, is there a way to mock auth and also the returning login and logout methods?
In this snippet:
beforeEach(() => addProviders([
AppComponent,
AngularFire
]);
You set (or override) the providers that will be used in your test.
That being said, you can create a different class, a mock if you will, and, using the { provide: originalClass, useClass: fakeClass } notation, provide it instead of the AngularFire actual class.
Something like this:
class AngularFireAuthMock extends AngularFireAuth { // added this class
public login() { ... }
public logout() { ... }
}
class AngularFireMock extends AngularFire { // added this class
public auth: AngularFireAuthMock;
}
beforeEach(() => addProviders([
AppComponent,
{ provide: AngularFire, useClass: AngularFireMock } // changed this line
]);
And the AngularFires in your tests will be AngularFireMocks.
hope it is not off the topic, but the easiest solution I have found how to mock the FirebaseDatabase.
var object = function() {
var obj = { valueChanges() {
return of({data:'data'});
}
}
return obj;
}
providers: [..., { provide : AngularFireDatabase,
useValue: {object : object }} ]
instead of data:'data' you can mock whatever data you need. The functions can be modified as you wish.
Similar to #jan, I made a mock using some utility functions:
import {AngularFireAuth} from '#angular/fire/auth';
import {AngularFireDatabase} from '#angular/fire/database';
import {auth} from 'firebase/app';
import { Observable, of, Subscription } from 'rxjs';
/**
* Mocks the Firebase auth by automatically logging in.
*/
export const AngularFireAuthMock = jasmine.createSpy('signInWithEmailAndPassword')
.and.returnValue(Promise.resolve({uid: 'fakeuser'}));
/**
* Mocks an AngularFireDatabase that always returns the given data for any path.
*/
export function mockAngularFireDatabase(data): AngularFireDatabase {
return {
object: (path: string): any => {
return {
valueChanges() {
return of(data);
}
}
}
} as AngularFireDatabase;
}
and then you can use them in your spec like this:
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TweakComponent ],
imports: [ MatDialogModule, RouterTestingModule ],
providers: [
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: AngularFireDatabase, useValue: mockAngularFireDatabase({testdata:'hi'})},
{ provide: AngularFireAuth, useValue: AngularFireAuthMock}
],
})
.compileComponents();
});