Reputation: 7015
I have this async method that I want to test:
async getGeneralStats(): Promise<GeneralStats> {
console.log("getGeneralStats()");
let recoveredGeneralStats = await this.storage.get(StorageKeyItems.GeneralStats);
console.log("recoveredGeneralStats", recoveredGeneralStats);
if (!recoveredGeneralStats) {
console.warn("There were no general stats, creating new one")
this.generalStats = new GeneralStats();
await this.storage.set(StorageKeyItems.GeneralStats, this.generalStats);
} else {
this.generalStats = recoveredGeneralStats;
console.log("Recovered GeneralStats : ");
console.dir(this.generalStats)
}
console.log("Returning from getGeneralStats")
return this.generalStats;
}
I want to test to things (to beging with): 1. If storage.get
is called and if storage.set
is called when storage.get
returns null.
The first test works and it is in this way:
it("should GET the GeneralStats from Storage Service ", ((done) => {
// Arrange
let spy = spyOn(storage, 'get').and.callFake((storageKey) => {
done();
return Promise.resolve(null);
})
// Act
statisticsProvider.getGeneralStats();
// Assert
expect(spy).toHaveBeenCalledWith(StorageKeyItems.GeneralStats);
}));
But when I try to run the second test in the same way It fails:
it("should SET GeneralStats in Storage if not General Stats were retrieved ", ((done) => {
// Arrange
spyOn(storage, 'get').and.callFake((storageKey) => {
console.log("spyGet was called");
return Promise.resolve(null);
})
let spySet = spyOn(storage, 'set').and.callFake((storageKey, value) => {
console.log("spySet was called");
done();
return Promise.resolve(null);
})
// Act
statisticsProvider.getGeneralStats();
// Assert
expect(spySet).toHaveBeenCalledWith(StorageKeyItems.GeneralStats,statisticsProvider.generalStats);
}));
The fail error says : "Expected spy set to have been called with [ 'GeneralStats', undefined ] but it was never called."
but the console log says other thing:
getGeneralStats()
spyGet was called
recoveredGeneralStats null
There were no general stats, creating new one
spySet was called
FAILED StatisticsProvider #getGeneralStats should SET GeneralStats in Storage if not General Stats were retrieved
Returning from getGeneralStats
ERROR Expected spy set to have been called with [ 'GeneralStats', undefined ] but it was never called.
I have no idea why it says it was not called when it was. I even tried to "cheat" and make a variable that changes to true when the spySet is called but it stills does not work.
it("should SET GeneralStats in Storage if not General Stats were retrieved ", ((done) => {
let spyWasCalled = false;
// Arrange
spyOn(storage, 'get').and.callFake((storageKey) => {
console.log("spyGet was called");
return Promise.resolve(null);
})
let spySet = spyOn(storage, 'set').and.callFake((storageKey, value) => {
console.log("spySet was called");
spyWasCalled = true;
return Promise.resolve(null);
})
// Act
statisticsProvider.getGeneralStats().then(() => {
done();
});
// Assert
expect(spyWasCalled).toBeTruthy();
}));
If I put the expect()
in a .then
after I call my method the test past but why I didn't need it before?
it("should SET GeneralStats in Storage if not General Stats were retrieved ", ((done) => {
// Arrange
spyOn(storage, 'get').and.callFake((storageKey) => {
console.log("spyGet was called");
return Promise.resolve(null);
})
let spySet = spyOn(storage, 'set').and.callFake((storageKey, value) => {
console.log("spySet was called");
return Promise.resolve(null);
})
// Act
statisticsProvider.getGeneralStats().then(() => {
// Assert
expect(spySet).toHaveBeenCalledWith(StorageKeyItems.GeneralStats, statisticsProvider.generalStats);
done();
});
}));
Upvotes: 0
Views: 46
Reputation: 636
Both test cases give inconsistent test results. Because:
expect()
may be executed before the target function completed its command due to asynchronous works it has. - When the test cases run, getGeneralStats()
gives a Promise
and may or may not move on to run expect()
which may result in failure.
done()
in the wrong place. The purpose of done()
is to be called when the asynchronous test case has been completed. So, you should call it after expect()
and when you're sure that all the asynchronous executions are finished. - in first (and second) test case, it might not run the expect()
at all! Because done()
may be called before expect()
inside those callFakes.
To validate, add more console.log()
before expect()
(or after too). So you can see if the callFake closure runs before or after the expect.
What I suggest
Since the function under test has asynchronous works, you have to expect the result in async manner too.
Jasmine has documented here.
You can either use async/await
, Promise
, or done()
. Here is the example using async/await
:
it("should SET GeneralStats in Storage if not General Stats were retrieved ", (async () => {
// Arrange
spyOn(storage, 'get').and.callFake((storageKey) => {
console.log("spyGet was called");
return Promise.resolve(null);
})
let spySet = spyOn(storage, 'set').and.callFake((storageKey, value) => {
console.log("spySet was called");
return Promise.resolve(null);
})
// Act
await statisticsProvider.getGeneralStats();
// Assert
expect(spySet).toHaveBeenCalledWith(StorageKeyItems.GeneralStats, statisticsProvider.generalStats);
}));
Upvotes: 1