I have this method that I am trying to write a unit test for. The test currently is just going to spyOn methods being called in the correct order.
handleSave() {
this.saving = true;
this.presenters.presentLoader({
message: "Uploading..."
});
this.loginService.loginToFirebase().subscribe(() => {
Promise.all(
this.credential.inputs.map((input) => this.putStorageItem(input))
)
.then((url) => {
this.presenters.dismissLoader();
this.presenters.presentAlert({
message: 'Successfully Uploaded Identification.',
buttons: ['Ok']
});
this.presenters.dismissModal(true);
this.saving = false
})
.catch((error) => {
this.presenters.dismissLoader();
this.presenters.presentAlert({
message: 'Something went wrong.'
});
this.saving = false
});
})
}
The Unit test so far works for the initial call of presentLoader but the subsequent dismissLoader (which is inside the loginToFirebase observable and the Promise.all doesn't get called (at least not before the observable and promises resolve)
In my before each I mock the putStorageItem
component.putStorageItem = (item) => Promise.resolve(true);
In and I mock the loginService with this.
{
provide: LoginService,
useValue: {
loginToFirebase: () => of(true)
}
},
My current test
it("call handle save should do stuff", () => {
let presenters = TestBed.inject(PresentersService);
spyOn(presenters, 'presentLoader');
spyOn(presenters, 'dismissLoader');
spyOn(presenters, 'presentAlert');
spyOn(presenters, 'dismissModal');
component.handleSave();
expect(presenters.presentLoader).toHaveBeenCalled();
expect(presenters.dismissLoader).toHaveBeenCalled();
expect(presenters.presentAlert).toHaveBeenCalled();
expect(presenters.dismissModal).toHaveBeenCalled();
});
putStorageItem
putStorageItem(input) {
const fileExt = input.file.metadata.name.split('.').slice(-1);
const filepath = ${input.label}.${fileExt}`;
let task;
if(input.file.metadata.isBase64) {
task = this.fireStorage.ref(filepath).put(this.util.b64toBlob(input.file.img), {contentType:input.file.metadata.type, customMetadata:{originalName:input.file.metadata.name}});
} else {
task = this.fireStorage.ref(filepath).put(input.file.img, {contentType:input.file.metadata.type, customMetadata:{originalName:input.file.metadata.name}});
}
input.fileProgress = task.percentageChanges();
return task.then((snapshot) => {
console.log('One success:', input.file)
}).catch((error) => {
console.log('One failed:', input.file, error.message)
});
}
CodePudding user response:
Since you're using Observables
and/or promises
, you need to use different techniques for testing. The techniques are:
- the
done
callback of Jasmine async/await
andfixture.whenStable()
of AngularwaitForAsync
andfixture.whenStable()
of AngularfakeAsync
andtick
of Angular
You can read more about asynchronous testing in Angular as well.
My favorite is fakeAsync
and tick
and I think it can help you in this scenario.
// !! wrap the test in a `fakeAsync` so you have better control of promises
it("call handle save should do stuff", fakeAsync(() => {
let presenters = TestBed.inject(PresentersService);
spyOn(presenters, 'presentLoader');
spyOn(presenters, 'dismissLoader');
spyOn(presenters, 'presentAlert');
spyOn(presenters, 'dismissModal');
component.handleSave();
// !! Call tick() to tell the test that before running the
// statements below the tick,
// ensure the promises in the component code have resolved
// since the expect statements rely on them.
tick();
expect(presenters.presentLoader).toHaveBeenCalled();
expect(presenters.dismissLoader).toHaveBeenCalled();
expect(presenters.presentAlert).toHaveBeenCalled();
expect(presenters.dismissModal).toHaveBeenCalled();
}));
The tick
can also be used to ensure the subscribe
of an observable stream has completed before continuing but I mainly use it for promises.