Comments (5)
Hi, I don't know if this is the right way, but this is how I did it.
First, I want to isolate my tests from all external dependencies that are not strictly necessary. So I will not import IonicModule
, I'm going to use NO_ERRORS_SCHEMA
to allow any property or any element on the template and avoid errors.
Then, I've created a simple mock for the ion-nav
component.
@Component({
selector: 'ion-nav',
template: ''
})
class IonNavMock {
@Input() root: any;
@Input() swipeBackEnabled: boolean;
setRoot = jasmine.createSpy('setRoot').and.returnValue(null);
}
It has the same selector as the real one, and it uses Jasmine to create a spy on the method we're mocking out.
This is how my TestBed looks like:
beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [ NO_ERRORS_SCHEMA ],
declarations: [MyApp, IonNavMock],
providers: [
{ provide: StatusBar, useClass: StatusBarMock },
{ provide: SplashScreen, useClass: SplashScreenMock },
{ provide: Platform, useClass: PlatformMock }
]
});
}));
Next step is to wrap the component's creation into an async function
(because of the promise used in the component's constructor)
beforeEach(async(() => {
fixture = TestBed.createComponent(MyApp);
component = fixture.componentInstance;
}));
The last thing I did is to change the way we get a reference for the ion-nav
, instead of using the class name we'll use the name used for querying, content
. In the app.component.ts
:
@ViewChild(Nav) nav: Nav;
becomes
@ViewChild('content') nav: Nav;
Now we are able to test using a spy the delayed setRoot
method.
it('should set root to Page1', () => {
expect(component.nav.setRoot).toHaveBeenCalledWith(Page1);
});
from ionic-unit-testing-example.
What i've achieved so far is injecting the NavController to the test method, create a spy and then wrap the expectiation inside a fakeAsync:
it('should set root to Page1', inject([NavController], (nav: NavController) => {
spyOn(nav, "setRoot");
fakeAsync(() => {
expect(nav.setRoot).toHaveBeenCalledWith(Page1);
expect(component.rootPage).toBe(Page1);
});
}));
This test does actually pass, but the jasmine console output shows loooong error messages, complaining:
ERROR: Error{rejection: Error{ngComponent: function Page1(navCtrl) { ... }, __zone_symbol__currentTask: ZoneTask{_zone: ..., runCount: ..., _zoneDelegates: ..., _state: ..., type: ..., source: ..., data: ..., scheduleFn: ..., cancelFn: ..., callback: ..., invoke: ...}}, promise: ZoneAwarePromise{__zone_symbol__state: 0, __zone_symbol__value: Error{ngComponent: ..., __zone_symbol__currentTask: ...}}, zone: Zone{_properties: Object{isAngularZone: ...}, _parent: Zone{_properties: ..., _parent: ..., _name: ..., _zoneDelegate: ...}, _name: 'angular', _zoneDelegate: ZoneDelegate{_taskCounts: ..., zone: ..., _parentDelegate: ..., _forkZS: ..., _forkDlgt: ..., _forkCurrZone: ..., _interceptZS: ..., _interceptDlgt: ..., _interceptCurrZone: ..., _invokeZS: ..., _invokeDlgt: ..., _invokeCurrZone: ..., _handleErrorZS: ..., _handleErrorDlgt: ..., _handleErrorCurrZone: ..., _scheduleTaskZS: ..., _scheduleTaskDlgt: ..., _scheduleTaskCurrZone: ..., _invokeTaskZS: ..., _invokeTaskDlgt: ..., _invokeTaskCurrZone: ..., _cancelTaskZS: ..., _cancelTaskDlgt: ..., _cancelTaskCurrZone: ..., _hasTaskZS: ..., _hasTaskDlgt: ..., _hasTaskDlgtOwner: ..., _hasTaskCurrZone: ...}}, task: ZoneTask{_zone: Zone{_properties: ..., _parent: ..., _name: ..., _zoneDelegate: ...}, runCount: 0, _zoneDelegates: null, _state: 'notScheduled', type: 'microTask', source: 'Promise.then', data: Object{__creationTrace__: ...}, scheduleFn: undefined, cancelFn: null, callback: function () { ... }, invoke: function () { ... }}, longStack: 'Error: Uncaught (in promise): Error: No component factory found for Page1. Did you add it to @NgModule.entryComponents?
Error: No component factory found for Page1. Did you add it to @NgModule.entryComponents?
at noComponentFactoryError (http://localhost:9876/base/karma-test-shim.js?649d1ac0157ac456d8bfbc1c867f4253eb1b78fc:3580:34)
.....
So how to solve this? How can an entry component be added to TestBed??
from ionic-unit-testing-example.
Thanks @datencia, thats a nice example. π
What i also did wrong (and did not mentioned in my question) was, that i included IonicModule.forRoot()
into TestBed configuration. I think that caused a lot of trouble, which i didn't understand until i tried your code - then conflicting components were reported..
I'm still interested: is there also a possibility to inject and fake NavController instead of mocking the directive?
from ionic-unit-testing-example.
Hi @spacepope sorry for the delay in the answer but I have had some days off and forgot to do it.
ion-nav
is the declarative component for a NavController
. I think that to mock the underlying NavController
you need to import the IonicModule
in order to access the real ion-nav
component, then you need to find a way to mock its NavController
. This is something I always avoid because the purpose of a spec is to test the component, not the services involved, and the time required to execute a simple spec can go from milliseconds to a few seconds.
However, this is how I did it:
- First, I've included the
Page1
into the TestBed overriding the testing module. This is necessary because TestBed doesn't supportentryComponents
(Note I'm including thePage1
under the declarations array as well).
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
MyApp,
Page1,
],
imports: [
IonicModule.forRoot(MyApp),
],
providers: [
{ provide: StatusBar, useClass: StatusBarMock },
{ provide: SplashScreen, useClass: SplashScreenMock },
{ provide: Platform, useClass: PlatformMock },
]
})
.overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [ Page1 ],
},
});
}));
- Second, I've create a spy on the
setRoot
method for the component nav instance in the secondbeforeEach
.
beforeEach(async(() => {
fixture = TestBed.createComponent(MyApp);
component = fixture.componentInstance;
spyOn(component.nav, 'setRoot');
// spyOn(component.nav, 'setRoot').and.callFake(() => console.log('our mocked function'));
}));
This is necessary because the setRoot
method is invoked in the constructor.
- Then I write the spec as follows:
it('should navigate to page1', () => {
expect(component.nav.setRoot).toHaveBeenCalledWith(Page1);
});
The magic here is that I'm 'mocking' the setRoot
method using spies.
I hope you find this useful and answer your question.
from ionic-unit-testing-example.
Thank you very much @datencia for your detailed explanations!
This should definitely be pinned in the docs or faq..
I am closing this now, as my questions are answered.
from ionic-unit-testing-example.
Related Issues (20)
- Ionic 5 with Angular? HOT 2
- HTTP Error 404 Not Found for node-sass 4.5.0 for Windows 64 bit HOT 1
- error was thrown in afterAll with angular 5 HOT 15
- Vivaldi supported? HOT 1
- Problem running test: Cannot read property 'afterCompile' HOT 6
- Ionic lifecycle methods not called HOT 1
- Karma throws StaticInjectorError HOT 1
- Uncaught Error: Module build failed: TypeError: Cannot read property 'afterCompile' of undefined HOT 3
- Tests fail due to: "TypeError: Cannot read property 'run' of null" HOT 5
- Unit test within async using fixture.detectChanges() fails HOT 5
- Valdiating Ionic-options
- ts-loader's default config makes Webpack extremely slow in larger projects HOT 7
- Question/Discussion: E2E headless browser
- Cannot read property Ionic of undefined HOT 2
- Error output is minified
- Error: StaticInjectorError(t)[t -> function(){}]:
- Update documentation for karma & jasmine versions
- How to see more descriptive/meaningful error for βAn error was thrown in afterAll\nUncaught [object Object] thrownβ in Continious integration build console command prompt
- ERROR in Entry module not found HOT 1
- Not able to click on a button with protractor HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ionic-unit-testing-example.