Reputation: 7719
I'm trying to create a spec.ts
for my application. Sadly this is using the LoadingController from ionic-angular
. Now when I'm trying to configure the module, I will need to provide it with the LoadingController (since it's in the constructor of the module).
The problem I'm currently running into is that the LoadingController wants to be provided with a App
object/instance. (_app: App
params)
I was desperate so I asked Ionic themselves. github #8539
But they closed my question because it was a question and not an issue, although I'm having issues realizing it which they haven't responded to. It would be a shame if this is impossible/no one knows how, since it's a pretty cool feature and it affects not just the LoadingController, f.e. the AlertController and ToastController are affected by this as well.
My testbed configuration atm:
TestBed.configureTestingModule({
declarations: [EventsPage],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{provide: APICaller, useValue: mockAPICaller},
{provide: NavController, useValue: mockNavController },
{provide: LoadingController, useValue: ???????}, //it seems like everything I try to enter here fails.
],
imports: [FormsModule]
});
And the EventsPage constructor:
constructor(public apiCaller: APICaller, public navCtrl: NavController,
public loadingCtrl: LoadingController){}
EDIT: usage of LoadingController
getEvents() {
// setup a loadingscreen
let loading = this.loadingCtrl.create({
content: "Loading..."
});
// present the loadingscreen
loading.present();
// reset the noEvents boolean.
this.noEvents = true;
// call the api and get all events
this.apiCaller.getEvents()
.subscribe(response => {
// response is list of events
this.events = response;
// if the event is not empty, set noEvents to false.
if (this.events.length > 0) {
this.noEvents = false;
}
// close the loading message.
loading.dismiss();
});
}
Which will then result in this loadingspinner (with different text)
Upvotes: 3
Views: 1134
Reputation: 208964
With this type of thing, you probably don't want to test anything in the UI (in regards to using the LoadingController
). What you should be testing is the behavior of the component. So when you create the mock for the LoadingController
what you want to do is spy on the critical methods, and your expectations should test to make sure that you are calling the methods on the LoadingController
. Doing this you can write tests like
expect(loadingController.someMethod).toHaveBeenCalled();
// or
expect(loadingController.someMethod).toHaveBeenCalledWith(args);
Your mocks don't have to follow the actual structure of the item being mocked either. For instance LoadingController.create
returns a Loading
object. In your mock, you don't need to have this. If you want you can just return the mock itself, when calling create
, and in the mock just have the methods that Loading
would have.
Remember that you are just testing the behavior of the controller. It doesn't matter what the mock LoadingController
actually does, just that you are able to called the methods, and check in the test to make sure they are called when they are expected to be called. Other than that, you should just assume the real LoadingController
works.
So you could have something like
let mockLoadingController = {
// Tried `create: jasmine.createSpy('create').and.returnValue(this)
// seem this doesn't work. We just create the spy later
create: (args: any) => { return this; },
present: jasmine.createSpy('present'),
dismiss: jasmine.createSpy('dismiss')
};
spyOn(mockLoadingController, 'create').and.returnValue(mockLoadingController);
{ provide: LoadingController, useValue: mockLoadingController }
Then in you tests you can just do something like
it('should create loader with args ...', () => {
...
expect(mockLoadingController.create).toHaveBeenCalledWith({...})
})
it('should present the loader', () => {
...
expect(mockLoadingController.present).toHaveBeenCalled();
})
it('should dismiss the loader when the api call returns', async(() => {
..
expect(mockLoadingController.dismiss).toHaveBeenCalled();
}))
Here's what I used right now to test
class LoadingController {
create(args: any) { return this; }
present() {}
dismiss() {}
}
@Component({
template: ''
})
class TestComponent {
constructor(private loadingController: LoadingController) {}
setEvents() {
let loading = this.loadingController.create({hello: 'world'});
loading.present();
loading.dismiss();
}
}
describe('component: TestComponent', () => {
let mockLoadingController;
beforeEach(async(() => {
mockLoadingController = {
create: (args: any) => { return this; },
present: jasmine.createSpy('present'),
dismiss: jasmine.createSpy('dismiss')
};
spyOn(mockLoadingController, 'create').and.returnValue(mockLoadingController);
TestBed.configureTestingModule({
declarations: [TestComponent],
providers: [
{ provide: LoadingController, useValue: mockLoadingController }
]
});
}));
it('should calling loading controller', () => {
let comp = TestBed.createComponent(TestComponent).componentInstance;
comp.setEvents();
expect(mockLoadingController.create).toHaveBeenCalledWith({ hello: 'world'});
expect(mockLoadingController.present).toHaveBeenCalled();
expect(mockLoadingController.dismiss).toHaveBeenCalled();
});
});
See Also:
Upvotes: 6