Why would Jasmine Spyon not be called - javascript

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);
});

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');

Jasmine Test Error : Expected Spy XYZ to have been called

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

How to spy on ejs render function with sinon

I want to check whether "res.render" is called with the correct arguments.
it("Renders the page where a new user can be created", done => {
const spy = sinon.spy(ejs, "render");
chai
.request(app)
.get("/users/create")
.end((err, res) => {
res.should.have.status(200);
spy.should.have.been.calledOnce;
done();
});
});
Even thought the render function gets called, the test report tells me that render function is never called.
Uncaught AssertionError: expected render to have been called exactly once, but it was called 0 times
How can I spy on a ejs render function that is called on the response object and assert whether it is called with the correct arguments?
EDIT: Our project is using express-ejs-layouts to render ejs files
If you call res.render() using EJS, it's not ejs.render() that gets called but ejs.renderFile() (source).
Following the code path for that method, it never calls ejs.render().
Spying on ejs.renderFile() is also not possible, because of how Sinon works (spying on ejs.renderFile replaces the exported reference to the renderFile method, but ejs uses the internal reference).
However, you can spy on ejs.__express, which is the function that Express will call for res.render():
const spy = sinon.spy(ejs, "__express");
EDIT: I now realise that you want to make sure if res.render() is being called with the correct arguments, so it would make more sense to spy on that method:
const { response } = require('express');
...
const spy = sinon.spy(response, 'render');

node.js how to get better error messages for async tests using mocha

A typical test in my node.js mocha suite looks like the following:
it("; client should do something", function(done) {
var doneFn = function(args) {
// run a bunch of asserts on args
client.events.removeListener(client.events.someEvent, userMuteFn);
done();
}
client.events.on(someEvent, doneFn);
client.triggerEvent();
});
The problem here is that if client.triggerEvent() doesn't do something right, or if the server breaks and never invokes someEvent, then done() will never get invoked. This leaves an ambiguous error for someone who hasn't worked with the testsuite before like:
Error: timeout of 10500ms exceeded. Ensure the done() callback is being called in this test.
My question is, is there a way to rewrite these tests, whether it's with mocha or in addition to another lib, that makes async work like this easier to follow. I'd love to be able to output something such as:
the callback doneFn() was never invoked after clients.event.on(...) was invoked
or perhaps something similar.
I'm not sure if using something such as promises would help this. More meaningful error messages for async/callback type code would be a great thing. If it means moving from callback/async to another workflow, I'm OK with that as well.
What are some solutions?
When you get a timeout error rather than a more precise error, the first thing to do is to check that your code does not swallow exceptions, and that it does not swallow promise rejections. Mocha is designed to detect these in your tests. Unless you do something unusual like running test code in your own VM or manipulate domains, Mocha will detect such failures, but if your code swallows them, there's nothing Mocha can do.
This being said, Mocha won't be able to tell you that done was not called because your implementation has a logic bug that causes it to fail to call the callback.
Here is what could be done with sinon to perform a post mortem after test failures. Let me stress that this is a proof of concept. If I wanted to use this on an ongoing basis, I'd develop a proper library.
var sinon = require("sinon");
var assert = require("assert");
// MyEmitter is just some code to test, created for the purpose of
// illustration.
var MyEmitter = require("./MyEmitter");
var emitter = new MyEmitter();
var postMortem;
beforeEach(function () {
postMortem = {
calledOnce: []
};
});
afterEach(function () {
// We perform the post mortem only if the test failed to run properly.
if (this.currentTest.state === "failed") {
postMortem.calledOnce.forEach(function (fn) {
if (!fn.calledOnce) {
// I'm not raising an exception here because it would cause further
// tests to be skipped by Mocha. Raising an exception in a hook is
// interpreted as "the test suite is broken" rather than "a test
// failed".
console.log("was not called once");
}
});
}
});
it("client should do something", function(done) {
var doneFn = function(args) {
// If you change this to false Mocha will give you a useful error. This is
// *not* due to the use of sinon. It is wholly due to the fact that
// `MyEmitter` does not swallow exceptions.
assert(true);
done();
};
// We create and register our spy so that we can conduct a post mortem if the
// test fails.
var spy = sinon.spy(doneFn);
postMortem.calledOnce.push(spy);
emitter.on("foo", spy);
emitter.triggerEvent("foo");
});
Here is the code of MyEmitter.js:
var EventEmitter = require("events");
function MyEmitter() {
EventEmitter.call(this);
}
MyEmitter.prototype = Object.create(EventEmitter.prototype);
MyEmitter.prototype.constructor = MyEmitter;
MyEmitter.prototype.triggerEvent = function (event) {
setTimeout(this.emit.bind(this, event), 1000);
};
module.exports = MyEmitter;
You should to listen to uncaughtException events, in addition to someEvent. This way you'll catch errors happening in client, and those will show up in your test report.
it("; client should do something", function(done) {
var doneFn = function(args) {
// run a bunch of asserts on args
client.events.removeListener(client.events.someEvent, userMuteFn);
done();
}
var failedFn = function(err) {
client.events.removeListener('uncaughtException', failedFn);
// propagate client error
done(err);
}
client.events.on(someEvent, doneFn);
client.events.on('uncaughtException', failedFn);
client.triggerEvent();
});
P.S. In case client is a child process, you also should listen to exit event, which will be triggered in case the child process dies.

Categories