Jasmine Test Error : Expected Spy XYZ to have been called - javascript

I have a method in component.ts file, which is calling a service method.
The code structure is like as following-
getDetails(){
if(!alreadyCached){
callLogFunction();
}
this.service.getData()
.then(res => {
//rest of the logic here
})
.catch(err=>{ //catch logic here })
}
My test block looks like this-
it('should call service.getData', ()=>{
spyOn(service,'getData').and.returnValue(Promise.resolve([]);
component.getDetails();
fixture.whenStable().then(()=>{
expect(service.getData).toHaveBeenCalled();
}
}
Why am I getting error that expected spy getData to have been called
It would be great if you can help me with testing then and catch block (how to write tests for them)
Thanks! :)

If you are trying to test that service.getData is called, as per your test name, you should write the test thus:
it('should call service.getData', ()=>{
const getDataSpy = spyOn(service,'getData');
component.getDetails();
expect(getDataSpy).toHaveBeenCalled();
}
That way you have tested only that which you want to test, that the service method is called, without having other distractions

Related

How to properly test statements in setInterval() in Angular?

I have this very simple code:
public async authenticate(username: string, password: string) {
const authenticationResponse = await this.dataProvider.authenticate(username, password);
if (authenticationResponse.result.code == 0) {
//start interval for periodically checking authentication info
this._authInfoIntervalId = setInterval(() => {
this.getAuthInfo();
}, 2000);
In my unit tests only line with this.getAuthInfo() is not concidered as covered. Is it somehow possible to test this? I have tried some approaches with jasmine.createSpy but nothing seemed to work (most likely because I was doing it wrong). Can someone please help me to get it right? Thanks
UPDATE: I tried something like this
it('should test interval somehow', () => {
const intervalCallback = jasmine.createSpy("getAuthInfo");
jasmine.clock().install();
service.authenticate('username', 'password');
jasmine.clock().tick(2001);
expect(intervalCallback).toHaveBeenCalled();
})
and test fails instantly with AuthenticationService should test interval somehow FAILED Expected spy getAuthInfo to have been called.
SOLUTION: I had to spyOn also on dataProvider so I got right response to actually reach that part of code with interval
it('should test interval somehow', async () => {
const intervalCallback = spyOn(service, 'getAuthInfo');
spyOn(dataProvider, 'authenticate').and.returnValue(Promise.resolve(authenticateMockResponse));
jasmine.clock().install();
await service.authenticate('username', 'password');
jasmine.clock().tick(2001);
expect(intervalCallback).toHaveBeenCalled();
});
jasmine.createSpy() should be used for creating a bare spy object, see the documentation. It doesn't have any connection to your service, it's not what you are looking for.
You want to spy a function on an existing object (in your case the service), for which you can use the spyOn function.
it('should test interval somehow', () => {
const intervalCallback = spyOn(service, 'getAuthInfo');
jasmine.clock().install();
service.authenticate('username', 'password');
jasmine.clock().tick(2001);
expect(intervalCallback).toHaveBeenCalled();
})
In Angular, you can use the async and fakeAsync functions from the #angular/core/testing module to properly test statements within a setInterval() function.
First, you'll need to wrap your test in the fakeAsync function, which allows you to use the tick() function to advance the virtual time. Next, you'll need to wrap the code you want to test within the setInterval() function in an async function.

Unit testing for backend and services methods calls with Jasmine

I started working with tests, more specifically with Jasmine, and I'm having some difficulty to test if the backend and some services methods are being called.
So basically I'm calling the forgotPassword method when the formulary is submitted and I was wondering how can I properly check if the API (apiSendPasswordResetLink) and the services methods (showLoader, showAlert and navigateTo) are being called as expected.
async forgotPassword() {
try {
console.log("1");
this.loadingService.showLoader();
console.log("2");
await this.userService
.apiSendPasswordResetLink(this.form.value['email'])
.toPromise();
console.log("3");
this.utilitiesService.showAlert(`We've emailed you a link to reset your password. Please check your mail box and spam.`);
console.log("4");
delay(1500);
this.navigationService.navigateTo('/login/auth');
console.log('5')
} catch (err) {
this.utilitiesService.showAlert(err);
} finally {
this.loadingService.hideLoader();
}
}
The test:
it('should submit email to reset password after submitting formulary', () => {
component.form.setValue({
email: 'test#test.io',
});
const loadingService = TestBed.inject(LoaderService);
const userService = TestBed.inject(UserService);
const utilitiesService = TestBed.inject(UtilitiesService);
const navigationService = TestBed.inject(NavigationService);
fixture.detectChanges();
const button = fixture.debugElement.nativeElement.querySelector('#button');
spyOn(component, 'forgotPassword').and.callThrough();
spyOn(loadingService, 'showLoader');
spyOn(userService, 'apiUserSendPasswordResetLink');
spyOn(utilitiesService, 'showAlert');
spyOn(navigationService, 'navigateTo');
// Submitting form
fixture.debugElement
.query(By.css('form'))
.triggerEventHandler('ngSubmit', null);
expect(component.form.valid).toEqual(true);
expect(button.disabled).toBeFalsy();
expect(component.forgotPassword).toHaveBeenCalled();
expect(loadingService.showLoader).toHaveBeenCalled();
expect(userService.apiUserSendPasswordResetLinkGet).toHaveBeenCalled();
expect(utilitiesService.showAlert).toHaveBeenCalled();
expect(navigationService.navigateTo).toHaveBeenCalled();
});
Every time I run and / or debug the test, I have the error
Expected spy navigateTo to have been called.
But the console never prints "3", which means showAlert is also not being called and I should also have the same error regarding showAlert spy to be called, but I don't.
I don't know if this problem has to do if the await call to the API or something else. I would like to know how can I fix it, so all the test can pass as expected.
Thank you!
When adding a spy on UserService#apiUserSendPasswordResetLink, without any spy strategy, it defaults to doing nothing. However, in your forgotPassword method, you are chaining the response of the call to a Promise wrapper and awaiting the resolution. Since apiUserSendPasswordResetLink is not invoked, I'm guessing that the promise is never resolved and the test gets stuck.
One simple way to resolve the issue is to add a strategy to the spy so that it returns a value:
spyOn(userService, 'apiUserSendPasswordResetLink').and.returnValue('whatever');

Why would Jasmine Spyon not be called

I have a test that is failing even though I am calling it. I am spying on the PrimeNG Message Service.
Below is some code that I have so far.
it('should throw error', () => {
const mockCall = spyOn(service, 'update$').and.throwError('Error');
const msgService = spyOn(messageService, 'add').and.callThrough();
expect(mockCall).toThrowError('Error');
expect(msgService).toHaveBeenCalled();
});
I am expecting this to pass this test since it gets called withing my update$ observable if there is an error. Here is the error
"Expected spy add to have been called"
As a rule of thumb you should not apply mocks to the method you are testing. You should be calling the method you are testing directly, then verify what happened. You should also avoid verifying your mocks. You've set up service.update$ to throw, that's what it will do, you do not need to verify it happened.
Your test should probably look something like this:
it('should throw error', () => {
// Set up
const mockCall = spyOn(service, 'update$').and.throwError('Error');
// Test
const message = {};
messageService.add(message);
// Verify
expect(mockCall).toHaveBeenCalledWith(message);
});

Jest testing with Node - Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout

I'm starting to test my code with Jest, and I can't make a seemingly simple test to pass. I am simply trying to check if what I receive from a Maogoose database request is an object.
The function fetchPosts() is working because I hooked it up with a React frontend and it is displaying the data correctly.
This is my function fetchPosts():
module.exports = {
fetchPosts() {
return new Promise((resolve, reject) => {
Posts.find({}).then(posts => {
if (posts) {
resolve(posts)
} else {
reject()
}
})
})
}
}
And my test:
it('should get a list of posts', function() {
return posts.fetchPosts().then(result => {
expect(typeof result).toBe('object')
})
})
This makes the test fail, and Jest says
'Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.'
QUESTION: How can I make this test pass?
You can expect asynchronous results using resolves, as shown in the Jest documentation.
In your case:
it('should get a list of posts', function() {
const result = posts.fetchPosts();
expect(result).resolves.toEqual(expect.any(Object));
})
…although I have a suspicion your list of posts is actually an array, so you probably want this:
it('should get a list of posts', function() {
const result = posts.fetchPosts();
expect(result).resolves.toEqual(expect.any(Array));
})
Another tip: You don't need to wrap the body of your fetchPost in an additional promise, you can simply return the promise you get from Posts.find and add a then to it, like this:
module.exports = {
fetchPosts() {
return Posts.find({}).then(posts => {
if (posts) {
return posts;
}
throw new Error('no posts'); // this will cause a promise rejection
})
}
}
It's also highly possible that you're not getting a response back from the DB at all from your test suite. Test suite's can call different environmental variables / configs that lead to different calls. This error can also be seen if no response is returned, as in - if someone blocks your IP from connecting, on and on.
Also if you are simply looking to increase the timeout, then you can do that by setting
jest.setTimeout(10000);
You can use this statement in beforeEach if you want to change the timeout for all your tests in that describe block or in the test/it/spec block if you want it for a single test.
For me none of the above worked so I tried older version of jest and it worked
npm i -D jest#25.2.7.
if you are using it with typescript make sure to degrade ts-jest as well
npm i -D jest#25.2.7 ts-jest#25.3.1

Test callback invocation at the end of promise chain

I am dealing with a code mixing node-style callbacks and Bluebird promises, and I need to write some unit tests for it.
In particular, cache.js exposes the init() function, which works with promises. It is then called by the doSomething() function in another file (e.g. index.js) which in turn accepts a callback that has to be invoked at the end of init().
Pseudocode is as follows:
// [ cache.js ]
function init() {
return performInitialisation()
.then((result) => return result);
}
// [ index.js ]
var cache = require('./cache');
function doSomething(callback) {
console.log('Enter');
cache.init()
.then(() => {
console.log('Invoking callback');
callback(null);
})
.catch((err) => {
console.log('Invoking callback with error');
callback(err);
});
console.log('Exit');
}
A possible unit test could be (showing only relevant code):
// [ index.test.js ]
...
var mockCache = sinon.mock(cache);
...
it('calls the callback on success', function(done) {
mockCache.expects('init')
.resolves({});
var callback = sinon.spy();
doSomething(callback);
expect(callback).to.have.been.calledOnce;
done();
});
This test passes, however changing the expectation to not.have.been.calledOnce also passes, which is wrong.
Also, console logs are out of sequence:
Enter
Exit
Invoking callback
I have looked at several possibilities, none of which worked:
Using chai-as-promised, e.g. expect(callback).to.eventually.have.been.calledOnce;
Refactoring doSomething() to be simply:
function doSomething(callback) {
cache.init()
.asCallback(callback);
}
Can anyone help me understand what I am doing wrong and how I can fix it please?
console logs are out of sequence
The logs are in the correct order because your Promise will be async meaning, at the very least, the internal console logs calls in then & catch will run on the next tick.
As to why the test is failing is the result of a couple of issues, first one is you don't appear to have sinon-chai configured correctly, or at best your calledOnce assertion isn't kicking in. Just to confirm, the top of your test file should something like:
const chai = require("chai");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
If you have that and it's still not working correctly then might be worth opening an issue on the sinon-chai lib, however, a simple workaround is to switch to sinon assertions e.g.
sinon.assert.calledOnce(callback)
Secondly, when you do eventually fix this, you'll probably find that the test will now fail...everytime. Reason being you've got the same problem in your test that you have with your logging - your asserting before the internal promise has had a chance to resolve. Simplest way of fixing this is actually using your done handler from Mocha as your assertion
mockCache.expects('init').resolves({});
doSomething(() => done());
In other words, if done get's called then you know the callback has been called :)
Following James' comment I revisited my tests like this:
it('calls the callback on success', function(done) {
mockCache.expects('init')
.resolves({});
doSomething(done);
});
it('calls the callback on error', function(done) {
mockCache.expects('init')
.rejects('Error');
doSomething((err) => {
if (err === 'Error') {
done();
} else {
done(err);
}
});
});

Categories