Closed mjosephd closed 6 years ago
Exactly the same problem right now also. ModalController mocks fine, would have thought it would be similar for ViewController
Also, someone had this issue (unresolved) yesterday over at http://stackoverflow.com/questions/40871317/ionic-2-viewcontroller-unit-testing
Hey, got this figured out. Heres how I got it working:
Create a mock object like this (I keep my all mocks in a mocks.ts file)
export class ViewControllerMock { public _setHeader(): any { return {} }; public _setIONContent(): any { return {} }; public _setIONContentRef(): any { return {} }; }
then provide the mock in the configureTestingModule providers array
{ provide: ViewController, useClass: ViewControllerMock },
Works a charm for me, tests all running fine now.
@MarkySparky The stack overflow question would actually be me as well. I'll try out your mock sample.
Though I am not sure if the issue should be closed as it does not work with Jasmine? Correct me if I am wrong but Jasmine's spies are essentially test doubles as well. This is an excerpt from their site. https://jasmine.github.io/2.0/introduction.html#section-Spies "Jasmine has test double functions called spies. A spy can stub any function and tracks calls to it and all arguments.
@mjosephd Yup, I get you. I'd like the option to use jasmine also, I was just wanting it working initially, then i'll maybe refactor once I have more unit tests in place to push out all the possible scenarios that arise with ionic 2 stubbing / mocking. I still use spies in the actual tests to check if the functions are called, so you are right, it would make sense to use it in the mock also. I'll maybe give it a go tomorrow with spies instead of a stubbed mock object.
I solved the problem by just using a ViewController instance:
const viewControllerStub = new ViewController();
...
{ provide: ViewController, useValue: viewControllerStub }
And for the functions I was interested in, I used a spy:
spyOn(viewControllerStub, "dismiss");
@Ritzlgrmft This is interesting. It gets rid of the problem of me mocking out stuff like:
ViewController.readReady()
and ViewController._setIONcontent()
.
However, I'm currently doing a set up where I mock viewController.viewEnter
inside a "fake" ViewControllerMock like how @MarkySparky has it. Doing useValue: viewControllerStub
replaces my ability to do that.
The problem I'm having with @MarkySparky's set up is, with the new release of Ionic 2.0 FINAL, this part is unmockable:
if (viewCtrl) {
// content has a view controller
viewCtrl._setIONContent(this);
viewCtrl._setIONContentRef(elementRef);
this._viewCtrlReadSub = viewCtrl.readReady.subscribe(function () {
_this._viewCtrlReadSub.unsubscribe();
_this._readDimensions();
});
this._viewCtrlWriteSub = viewCtrl.writeReady.subscribe(function () {
_this._viewCtrlWriteSub.unsubscribe();
_this._writeDimensions();
});
The this._viewCtrlReadSub
part requires a new Observable
mock, but even after putting that into my ViewControllerMock
, it gives me a "cant unsubscribe _this._viewCtrlReadSub"`. More specifically, "Cannot read property 'unsubscribe' of undefined".
I'm gonna result to your viewControllerStub
technique, but I'll have to find a new way to mock places where I call viewCtrl.willEnter
.
Right now I'm using a mock class for viewController as below, and it takes care of the "readReady" issue as well:
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;
}
}
And then I add it in the TestBed module "providers":
{provide: ViewController, useClass: ViewControllerMock},
Now, this is a very simplistic but comprehensive mock. One may need to still add a method or modify it, but it will get rid of that "readyReady" and "writeReady" errors for sure.
I'm using ionic-angular@3.6.1, there are built-in mock generators.
Source Code:
import {Component, Inject, Input} from "@angular/core";
import {ViewController} from "ionic-angular";
@Component({
selector: "modal-header",
templateUrl: "./modal-header.component.html",
styleUrls: ["./modal-header.component.scss"]
})
export class ModalHeaderComponent {
@Input()
title: string;
constructor(public viewCtrl: ViewController) {
}
dismiss() {
return this.viewCtrl.dismiss();
}
}
Test Code:
import {async, ComponentFixture, TestBed} from "@angular/core/testing";
import {ModalHeaderComponent} from "./modal-header.component";
import {App, Config, IonicModule, Platform, ViewController} from "ionic-angular";
import {mockApp, mockConfig, mockPlatform, mockView} from "ionic-angular/util/mock-providers";
describe("ModalHeaderComponent", () => {
let component: ModalHeaderComponent;
let fixture: ComponentFixture<ModalHeaderComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [IonicModule],
providers: [
{provide: ViewController, useValue: mockView()},
{provide: Config, useValue: mockConfig()},
{provide: Platform, useValue: mockPlatform()},
{provide: App, useValue: mockApp()},
],
declarations: [ModalHeaderComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ModalHeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should be created", () => {
expect(component).toBeTruthy();
});
});
@beenotung i'm using jest, and I tried use the mock*()
functions of ionic like you said, but an error happens with import { NgZone } from '@angular/core'
of mock-providers.ts
I think that jest add patches into Zone, and problems like this happens when import him explicitly by another file instead the own angular
lifecycle.
So, I'm using the mock class approach suggested by @Ritzlgrmft and @Maziar-Fotouhi and it's working fine!! :smile:
Thanks for the issue! This issue is being closed due to inactivity. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.
Thank you for using Ionic!
Ionic version: (check one with "x")
[ ] 1.x [X] 2.x
I'm submitting a ... (check one with "x")
[X] bug report [ ] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/
Current behavior:
Injecting and mocking out ViewController for unit tests causes two separate issues.
1) The first error being
Failed: Can't resolve all parameters for ViewController: (?, ?, ?).
for when there is no mocking out of the ViewController.2) When creating a jasmine spy or mocking out the ViewController
TypeError: viewCtrl._setHeader is not a function
.Expected behavior:
NavController seems to work with and without mocking or using jasmine.
So by the same means I would assume ViewController should work for both scenarios.
Steps to reproduce:
1) Create a sample Unit test, there is no need to even USE ViewController anywhere in the components that are being unit tested. 2) Simply Inject
ViewController
to theproviders
array in theTestBed.configureTestingModule
function, this would cause the first error mentioned 3) Use a mock or jasmine double like the following for the second error{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
Related code:
The below is a sample unit test which uses
{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
Other information:
Saw a similar issue mentioned here https://github.com/driftyco/ionic/issues/9331
The exception mentioned in that issue is similar to the first exception mentioned here.
Error: Can't resolve all parameters for NavParams: (?).
However unlike ViewController when I try to create a jasmine spy for NavParams I do not get any errors like the one I am getting when I create a jasmine spy for ViewController.
Plain Injection DOES NOT WORK :
providers: [...,NavParams,...] DOES NOT WORK :
providers: [...,ViewController,...]Using Jasmine Spies in
providers: [....]
array WORKS :{ provide: NavParams, useClass: class { NavParams = jasmine.createSpy("navParams"); } },
DOES NOT WORK :{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
Referenced the helpful https://github.com/lathonez/clicker repository as a base for setting up unit tests in our project. Additionally before posting the issue here also cloned the clicker repository and tried adding ViewController simply to the providers array in the beforeEach section and got the same issues mentioned.
The function that is said to be not a function in the second error https://github.com/driftyco/ionic/blob/6b3e2ed447340cdd35c328c96aa7cfa5f34eb214/src/navigation/view-controller.ts#L364
Error 1 Log
Error 2 Log
Ionic info: