Spy method from dynamically obtained object - javascript

I use mongoose and I have login function, that tries to find user and then valid his password (I will not describe here all logic of this function, but only major parts to show you my problem).
function login(req, res) {
...
User.findOne(...)
.then((user) => {
user.validPassword(...);
...
});
}
I have defined a User model that contains a validPassword method and all work fine but I have trouble to spy validPassword method.
I use Jasmine to my tests and I tried to do this in this way:
const user = new User(...);
spyOn(user, 'validPassword').and.callThrough();
expect(user.validPassword).toHaveBeenCalled(); // was never called :(
And of course I called login function. When I test findOne method in this way, it works fine, but it is simpler because I call method from User constructor:
spyOn(User, 'findOne').and.callThrough();
expect(User.findOne).toHaveBeenCalled(); // this works fine!
I think my problem is related to different instances, because findOne method resolve a new user object for me and in the tests I create a second one, what is absolutely different object, but I'm not sure about this conjecture.
Can you tell me how to repair this?

Ok I fixed this.
1) I found solution of my problem here
2) I noticed that findOne method returns promise and then I do async task, so I had to check it with 'done' function before start testing.
function login(req, res) {
...
return User.findOne(...)
.then((user) => {
user.validPassword(...);
...
});
}
In jasmine:
beforeAll((done) => {
spyOn(User.prototype, 'validPassword').and.callThrough();
login(...).then(() => done());
}
it('calls validPassword', () => {
expect(User.prototype.validPassword).toHaveBeenCalled(); // works :)
});

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

Express JavaScript Supertest Expect specific json fields

I'm attempting to write some unit tests for API endpoints, and decided on JavaScript Express w/ Supertest. I've got the basic downs, but running into issues checking the response for a specific field. I want to parse the body and check if an expected field returns and has the correct value. Most everything I've seen online uses this method, but when I try it always passes, even when I enter values I know don't exist in the JSON. Any advice? Here is my code snippet:
describe('GET category', function () {
it('response w/ only injury returned', function () {
request('endpoint')
.get('path')
.set('header', 'token')
.expect(200)
.then(response => {
console.assert(response.body, "Baseball")
})
})
});
I have also tried changing the .then to .expect, with same results. If I do response.body.specificFieldinBody I get similar results. Any help?
You can do with your way - use .then syntax, but I think use Supertest assertion syntax will be good.
This mean, use response.body.should.have.property("Baseball"); instead of console.assert(response.body, "Baseball") (Baseball is your special field).
Or, My suggestion is creating a re-use code: Put a assertion callback function to a next expects section.
const isIncludeField = function (fieldName) {
return function (res) {
res.body.should.have.property(fieldName);
};
}
describe('GET category', function () {
it('response w/ only injury returned', function () {
request('endpoint')
.get('path')
.set('header', 'token')
.expect(200)
.expect(isIncludeField('Baseball')) //
.end(done); // call done callback
})
});

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

Best way to pass route params between callbacks in expressjs

I've tried to search for a similar problem on here but suprisingly couldn't find one posted already.
I use expressjs v4 framework and I'm constructing my routes like this:
'use strict';
let express = require('express');
let router = express.Router();
let users = require('./modules/users');
router.post('/',users.add);
router.put('/edit/:id',users.edit);
As you can see above, I'm requiring let users = require('./modules/users')
Now the users module looks (let's say) like this:
'use strict';
let usersDbModule = require('...');
let users = {
'add': (req, res, next) => {
let callback = (err, record) => {
//...do something
users.function1(record)
}
usersDbModule.save(req, callback);
},
'function1': (record) => {
users.function2()
},
'function2': () => {
//...do something with next() function
}
}
You can notice, that router from the first code block is using module's add function. add function it's a standard express middleware function but now the things are getting more complicated.
As you can see, add function has next as one of the params, now I'm doing some complex callbacks calls from different functions and let's say that in the end I want to call next in function2.
My question is, what is the best way of passing req, res and next params between different callback functions within the same module.
I come up with 3 different methods of doing it:
Method 1:
Pass req, res or next if necessary around to all the functions in the chain so in this case I would have to pass next to callback than to function1 and than from function1 to function2.
Not the best way in my opinion, difficult to maintain, read and probably test as well.
Method 2:
Wrap function1 and function2 with closures in the add passing all the necessary params. In this particular case I would have to wrap only function2 with closure passing next so it would looks something like this:
'add': (req, res, next) => {
users.function2(next);
//....rest of the code of the function add
}
And than the function2 itself:
'function2': (next) => {4
return () => {
//...now I have access to next here
// without a need to pass it to each and every
// function in the chain
}
}
Method 3:
Append all the necessary functions/variables to res.locals and pass only res object around.
It has exactly the same problem as Method 1 so I would personally be in favour of Method 2 but not sure if it doesn't make the code less readable and maybe there are some other problems with it, haven't tested it in production nor in development environment with the team.
I would really like to hear what are you guys using and how it plays in your projects/teams. Any preferences, best practices, best patterns ? Please share, I really want to know what's the best way.
Maybe there is even better way of doing it ?
All feedback greatly appreciated!
Real life example:
Example usage for function1 & function2 and possibly more...
Let's say we have an adapter that fetches data from an external API, than it needs to save the data into a database, and return a response. Let's also assume that the data returned from the API expires after 5s. If the client hits the route within 5s span, it gets the data from the database, if time between calls was longer, than it repeats the operation calling the API.
This would be of course more complicated than just function1 and function2. It would require a lot of callback functions, both from the adapter and the database, also separate functions for fetching data from the database, adapter, saving data into a database, and eventually deleting data from the database, it gives at least 4 callback functions already.
I think that mix express and app logic is not a good idea.
I use next way in my project
// middlewares/auth.js
// Example middleware
exports.isAdmin = function (req, res, next) {
if (smth-admin-check)
next();
else
next (new Error(403));
}
// routes/index.js
// Include only modules from /routes
let user = require('./user');
let auth = require('../middlewares/auth');
...
app.get('/user/:id(\\d+)', user.get);
app.post('/user', auth.isAdmin, user.post); // only admin can add user
// routes/user.js
// Call model methods and render/send data to browser
// Don't know about db
let User = require('/models/user');
...
exports.get = function(req, res, next) {
let id = req.params.id;
// I cache most data in memory to avoid callback-hell
// But in common case code like below
User.get(id, function(err, u) {
if (!u)
return next(new Error('Bad id'));
... render page or send json ...
});
}
...
exports.post = function(req, res, next) { ... }
// models/user.js
// Encapsulate user logic
// Don't use any express features
let db = require('my-db');
...
class User {
get(id, callback) { ... }
add(data, callback) { ... } // return Error or new user
...
}

Categories