I'm new to unit testing in Node and hit a road block when it comes to promises. My module performs a search through the ApiController which then returns a promise. Depending on its resolve status, it invokes one of two private functions within the module.
Module:
module.exports.process = function(req, res) {
let searchTerm = req.query.searchValue;
ApiController.fetchResults(searchTerm)
.then((results) => onReturnedResults(results), (err) => onNoResults());
function onReturnedResults(results) {
req.app.set('searchResults', results);
res.redirect('/results');
}
function onNoResults() {
res.render('search/search', { noResultsFound: true });
}
};
Test:
var res = {
viewName: '', data : {},
render: function(view, viewData) {
this.view = view;
this.viewData = viewData;
}
};
var req = { query: { searchValue: 'doesnotexist' } }
describe('When searching for results that do not exist', function() {
it('should display a no results found message', function(done) {
let expectedResult = {
noResultsFound: true;
}
SearchController.process(req, res);
// Assert...
expect(res.viewData).to.be.equal(expectedResult);
done();
});
})
What are the best practices around this and how can I 'mock' the fetchResults returned promise so that it doesn't actually fetch results from the API (whilst still invoking the private function)?
If methods are scoped as closures or are private to modules, then there is no way to access them and can only be verified indirectly. To provide a test implementation of fetchResults it has to be exposed in some way.
One way is to require ApiController (this may already be happening) and then use proxyquire or mockery to override the dependency. I've worked with many test suite, who's only way to interact and provide test dependencies are through patching require. Test suites become a nightmare using this method. Cleanup, unpatching, cascading patches, all become huge time sinks, and would recommend against it if there are any other options.
Another way is to bake in the ability to change fetchResults implementation into your objects. In addition to testing, making fetchResults configurable will help insulate your code from future changes, by helping to minimize the impact to process when/if the way you need to fetch results changes:
var Results = (function() {
return {
process: function(req, res) {
let searchTerm = req.query.searchValue;
this.fetchResults(searchTerm)
.then((results) => this.onReturnedResults(req, res, results), (err) => this.onNoResults(res)),
fetchResults: ApiController.fetchResults,
onReturnedResults: function(req, res, results) {
req.app.set('searchResults', results);
res.redirect('/results');
},
onNoResults: function(res) {
res.render('search/search', { noResultsFound: true });
}
};
})()
(i haven't used javascript in a while, so i'm not sure of the best way to model the above object, but the important part is that if your test wants to override an implementation than the implementation has to be exposed, which the above code should do)
Now there is a Results object with the fetchResults method exposed in a way that's easily overridable
var fakeFetchResults = sinon.stub();
fakeFetchResults.return(aFakePromiseWithResultsToTriggerResultsCodePath);
Results.fetchResult = fakeFetchResults;
The above exposes the return function and the error function, which also allow for isolated unit tests of that functionality.
Using process is done through the results object, your controller could register
Results.process
Related
When running API tests in Node.js I often find myself repeating whole blocks of it statements with slightly different assertions. This seems a waste and I'd like it to respect DRY principles.
Let's take the following as an example:-
const { expect } = require('chai');
const got = require('got');
const core = require('../Libraries/CoreFunctions')
describe('Some description', function() {
beforeEach(function() {
})
it('should do something', function(done) {
got.get('https://someurl.com',
{headers: core.headers()})
.then(res => {
core.respDateCode(res);
console.log(core.prettyJSON(res));
expect(core.something).to.be.lessThan(2000);
done()
})
.catch(err => {
console.log('Error: ', err.message);
});
}).timeout(5000);
it('should do something else', function(done) {
got.get('https://someurl.com',
{headers: core.headers()})
.then(res => {
core.respDateCode(res);
console.log(core.prettyJSON(res));
expect(core.somethingElse).to.be.greaterThan(1000);
done()
})
.catch(err => {
console.log('Error: ', err.message);
});
}).timeout(5000);
});
I'm looking for suggestions as to how best to refactor the above to reduce repetition?
Move the logic for fetching into a seperate file. You can then encapsulate every request into a function (with no parameters, so if API URL changes, your tests won't have to change). Every test should call the function under test explicitly in the "it" block, so it is quickly apparent what is being tested. If you have a lot of repeated setup code, you can move that into a function.
A nice side effect of the isolation of the API calls is that you will end up with a client for the API, that is actually being tested at the same time as your API.
Don't be afraid of your test code being duplicated at the high level. Basically "given this setup, when function under test is called, then this happens". You can put test setup into other functions, but don't overdo it, as you might risk not being able to tell what actually happened when looking at the test. Also, never abstract away the function under test.
const { expect } = require('chai');
const gotClient = require('../somewhere/client/uses/got');
const core = require('../Libraries/CoreFunctions')
describe('Some description', function() {
it('should do something', async function(done) {
// given
const res = await gotClient.resource.getOne()
// when
core.functionThatIsBeingTested(res);
// then
expect(core.something).to.be.lessThan(2000);
done()
}).timeout(5000);
it('should do something else', async function(done) {
// given
const res = await gotClient.resource.getOne()
// when
core.functionThatIsBeingTested(res);
// then
console.log(core.prettyJSON(res));
expect(core.somethingElse).to.be.greaterThan(1000);
done()
}).timeout(5000);
});
Notice, the only real difference between this version and your version is that in my version you don't need to concern yourself with the url and the headers, which makes the code more readable and easier to understand. It would be even better if client was named after the API it was fetching and the resource was a name of the resource.
Just as an example:
const res = await twilio.phoneNumbers.availableForPurchase()
I have a TypeScript project which I would like to deploy as JS NPM package. This package performs some http requests using rxjs ajax functions. Now I would like to write tests for these methods.
At some point I have a method like this (simplified!):
getAllUsers(): Observable<AjaxResponse> {
return ajax.get(this.apiUrl + '/users');
}
I know about basic testing, for example with spyOn I can mock a response from the server. But how would I actually test the http request?
The documentation of jasmine says that I cannot do async work in the it part, but in the beforeEach: https://jasmine.github.io/tutorials/async
Would this be the correct approach to test the API?
let value: AjaxResponse;
let error: AjaxError;
beforeEach((done) => {
const user = new UsersApi();
user.getAllUsers().subscribe(
(_value: any) => {
value = _value;
done();
},
(_error: any) => {
error = _error;
done();
}
);
});
it("should test the actual http request", () => {
// Test here something
// expect(value).toBe...
// expect(error).toBe...
});
I couldn't think of another approach how to do the async work...
You need to mock ajax.get to return an Observable that emits values that you want to test.
This is done depending on how ajax is declared in your file that contains user.getAllUsers method.
It'd be ideal if UsersApi() had ajax passed into it (pure function style) because then you could just do something like this:
e.g.
class UsersApi {
public ajax;
constructor(ajax) {
this.ajax = ajax;
}
getAllUsers() {
return this.ajax.get(....)
}
}
Edit: Passing in dependencies (aka dependency injection) is one thing that makes modules like this significantly easier to test - consider doing it!
Then you could very easily mock your tests out like this:
const someSuccessfulResponse = ...
const someFailedResponse = ...
const ajaxWithSuccess = {
get:jest.fn(() => of(someSuccessfulResponse))
}
const ajaxWithFailed = {
get:jest.fn(() => throwError(someFailedResponse))
}
describe('my test suite',() => {
it("should test a successful response", (done) => {
const user = new UsersApi(ajaxWithSuccess);
user.getAllUsers().subscribe(d => {
expect(d).toBe(someSuccessfulResponse);
done();
});
});
it("should test a failed response", (done) => {
const user = new UsersApi(ajaxWithFailed);
user.getAllUsers().subscribe(null,error => {
expect(d).toBe(someFailedResponse);
done();
});
});
});
Note: You DO NOT want to test the actual API request. You want to test that your code successfully handles whatever API responses you think it could receive. Think about it, how are you going to test if a failed API response is handled correctly by your code if your API always returns 200s?
EDIT #27: The above code works fine for me when I run jest, not totally clear on why jasmine (doesn't jest run on jasmine?) says it can't do async in it's. In any case, you could just change the code above to set everything up in the beforeEach and just do your expects in the it's.
I have a module, for the purposes of learning testing, that looks like this:
api.js
import axios from "axios";
const BASE_URL = "https://jsonplaceholder.typicode.com/";
const URI_USERS = 'users/';
export async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
export async function fetchUsers() {
return makeApiCall(URI_USERS);
}
export async function fetchUser(id) {
return makeApiCall(URI_USERS + id);
}
export async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => fetchUser(id)));
return users.map(user => parseUser(user));
}
export function parseUser(user) {
return `${user.name}:${user.username}`;
}
Pretty straight forward stuff.
Now I want to test that fetchUserStrings method, and to do that I want to mock/spy on both fetchUser and parseUser. At the same time - I don't want the behaviour of parseUser to stay mocked - for when I'm actually testing that.
I run in the problem that it seems that it is not possible to mock/spy on functions within the same module.
Here are the resources I've read about it:
How to mock a specific module function? Jest github issue. (100+ thumbs up).
where we're told:
Supporting the above by mocking a function after requiring a module is impossible in JavaScript – there is (almost) no way to retrieve the binding that foo refers to and modify it.
The way that jest-mock works is that it runs the module code in isolation and then retrieves the metadata of a module and creates mock functions. Again, in this case it won't have any way to modify the local binding of foo.
Refer to the functions via an object
The solution he proposes is ES5 - but the modern equivalent is described in this blog post:
https://luetkemj.github.io/170421/mocking-modules-in-jest/
Where, instead of calling my functions directly, I refer to them via an object like:
api.js
async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
async function fetchUsers() {
return lib.makeApiCall(URI_USERS);
}
async function fetchUser(id) {
return lib.makeApiCall(URI_USERS + id);
}
async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => lib.fetchUser(id)));
return users.map(user => lib.parseUser(user));
}
function parseUser(user) {
return `${user.name}:${user.username}`;
}
const lib = {
makeApiCall,
fetchUsers,
fetchUser,
fetchUserStrings,
parseUser
};
export default lib;
Other posts that suggest this solution:
https://groups.google.com/forum/#!topic/sinonjs/bPZYl6jjMdg
https://stackoverflow.com/a/45288360/1068446
And this one seems to be a variant of the same idea:
https://stackoverflow.com/a/47976589/1068446
Break the object into modules
An alternative, is that I would break my module up, such that I'm never calling functions directly within each other.
eg.
api.js
import axios from "axios";
const BASE_URL = "https://jsonplaceholder.typicode.com/";
export async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
user-api.js
import {makeApiCall} from "./api";
export async function fetchUsers() {
return makeApiCall(URI_USERS);
}
export async function fetchUser(id) {
return makeApiCall(URI_USERS + id);
}
user-service.js
import {fetchUser} from "./user-api.js";
import {parseUser} from "./user-parser.js";
export async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => lib.fetchUser(id)));
return ids.map(user => lib.parseUser(user));
}
user-parser.js
export function parseUser(user) {
return `${user.name}:${user.username}`;
}
And that way I can mock the dependency modules when I'm testing the dependant module, no worries.
But I'm not sure that breaking up the modules like this is even feasible - I imagine that there might be a circumstance where you have circular dependencies.
There are some alternatives:
Dependency injection in the function:
https://stackoverflow.com/a/47804180/1068446
This one looks ugly as though, imo.
Use babel-rewire plugin
https://stackoverflow.com/a/52725067/1068446
I have to admit - I haven't looked at this much.
Split your test into multiple files
Am investigating this one now.
My question: This is all quite a frustrating and fiddly way of testing - is there a standard, nice and easy, way people are writing unit tests in 2018, that specifically solve this issue?
As you've already discovered attempting to directly test an ES6 module is extremely painful. In your situation it sounds like you are transpiling the ES6 module rather than testing it directly, which would likely generate code that looks something like this:
async function makeApiCall(uri) {
...
}
module.exports.makeApiCall = makeApiCall;
Since the other methods are calling makeApiCall directly, rather than the export, even if you tried to mock the export nothing would happen. As it stands ES6 module exports are immutable, so even if you did not transpile the module you would likely still have issues.
Attaching everything to a "lib" object is probably the easiest way to get going, but it feels like a hack, not a solution. Alternatively using a library that can rewire the module is a potential solution, but its extremely hokey and in my opinion it smells. Usually when you're running into this type of code smell you have a design problem.
Splitting up the modules into tiny pieces feels like a poor mans dependency injection, and as you've stated you'll likely run into issues quickly. Real dependency injection is probably the most robust solution, but it's something you need to build from the ground up, it's not something that you can just plug into an existing project and expect to have things working immediately.
My suggestion? Create classes and use them for testing instead, then just make the module a thin wrapper over an instance of the class. Since you're using a class you'll always be referencing your method calls using a centralized object (the this object) which will allow you to mock out the things you need. Using a class will also give you an opportunity to inject data when you construct the class, giving you extremely fine grained control in your tests.
Let's refactor your api module to use a class:
import axios from 'axios';
export class ApiClient {
constructor({baseUrl, client}) {
this.baseUrl = baseUrl;
this.client = client;
}
async makeApiCall(uri) {
try {
const response = await this.client(`${this.baseUrl}${uri}`);
return response.data;
} catch (err) {
throw err.message;
}
}
async fetchUsers() {
return this.makeApiCall('/users');
}
async fetchUser(id) {
return this.makeApiCall(`/users/${id}`);
}
async fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => this.fetchUser(id)));
return users.map(user => this.parseUser(user));
}
parseUser(user) {
return `${user.name}:${user.username}`;
}
}
export default new ApiClient({
url: "https://jsonplaceholder.typicode.com/",
client: axios
});
Now lets create some tests for the ApiClient class:
import {ApiClient} from './api';
describe('api tests', () => {
let api;
beforeEach(() => {
api = new ApiClient({
baseUrl: 'http://test.com',
client: jest.fn()
});
});
it('makeApiCall should use client', async () => {
const response = {data: []};
api.client.mockResolvedValue(response);
const value = await api.makeApiCall('/foo');
expect(api.client).toHaveBeenCalledWith('http://test.com/foo');
expect(value).toBe(response.data);
});
it('fetchUsers should call makeApiCall', async () => {
const value = [];
jest.spyOn(api, 'makeApiCall').mockResolvedValue(value);
const users = await api.fetchUsers();
expect(api.makeApiCall).toHaveBeenCalledWith('/users');
expect(users).toBe(value);
});
});
I should note that I have not tested if the provided code works, but hopefully the concept is clear enough.
I’m new to node and mongo after 15 years of VB6 and MySql. I’m sure this is not what my final program will use but I need to get a basic understanding of how to call a function in another module and get results back.
I want a module to have a function to open a DB, find in a collection and return the results. I may want to add a couple more functions in that module for other collections too. For now I need it as simple as possible, I can add error handlers, etc later. I been on this for days trying different methods, module.exports={… around the function and with out it, .send, return all with no luck. I understand it’s async so the program may have passed the display point before the data is there.
Here’s what I’ve tried with Mongo running a database of db1 with a collection of col1.
Db1.js
var MongoClient = require('mongodb').MongoClient;
module.exports = {
FindinCol1 : function funk1(req, res) {
MongoClient.connect("mongodb://localhost:27017/db1", function (err,db) {
if (err) {
return console.dir(err);
}
var collection = db.collection('col1');
collection.find().toArray(function (err, items) {
console.log(items);
// res.send(items);
}
);
});
}
};
app.js
a=require('./db1');
b=a.FindinCol1();
console.log(b);
Console.log(items) works when the 'FindinCol1' calls but not console.log(b)(returns 'undefined') so I'm not getting the return or I'm pasted it by the time is returns. I’ve read dozens of post and watched dozens of videos but I'm still stuck at this point. Any help would be greatly appreciated.
As mentioned in another answer, this code is asynchronous, you can't simply return the value you want down the chain of callbacks (nested functions). You need to expose some interface that lets you signal the calling code once you have the value desired (hence, calling them back, or callback).
There is a callback example provided in another answer, but there is an alternative option definitely worth exploring: promises.
Instead of a callback function you call with the desired results, the module returns a promise that can enter two states, fulfilled or rejected. The calling code waits for the promise to enter one of these two states, the appropriate function being called when it does. The module triggers the state change by resolveing or rejecting. Anyways, here is an example using promises:
Db1.js:
// db1.js
var MongoClient = require('mongodb').MongoClient;
/*
node.js has native support for promises in recent versions.
If you are using an older version there are several libraries available:
bluebird, rsvp, Q. I'll use rsvp here as I'm familiar with it.
*/
var Promise = require('rsvp').Promise;
module.exports = {
FindinCol1: function() {
return new Promise(function(resolve, reject) {
MongoClient.connect('mongodb://localhost:27017/db1', function(err, db) {
if (err) {
reject(err);
} else {
resolve(db);
}
}
}).then(function(db) {
return new Promise(function(resolve, reject) {
var collection = db.collection('col1');
collection.find().toArray(function(err, items) {
if (err) {
reject(err);
} else {
console.log(items);
resolve(items);
}
});
});
});
}
};
// app.js
var db = require('./db1');
db.FindinCol1().then(function(items) {
console.info('The promise was fulfilled with items!', items);
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
Now, more up to date versions of the node.js mongodb driver have native support for promises, you don't have to do any work to wrap callbacks in promises like above. This is a much better example if you are using an up to date driver:
// db1.js
var MongoClient = require('mongodb').MongoClient;
module.exports = {
FindinCol1: function() {
return MongoClient.connect('mongodb://localhost:27017/db1').then(function(db) {
var collection = db.collection('col1');
return collection.find().toArray();
}).then(function(items) {
console.log(items);
return items;
});
}
};
// app.js
var db = require('./db1');
db.FindinCol1().then(function(items) {
console.info('The promise was fulfilled with items!', items);
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
Promises provide an excellent method for asynchronous control flow, I highly recommend spending some time familiarizing yourself with them.
Yes, this is an async code and with a return you will get the MongoClient object or nothing, based on where you put.
You should use a callback parameter:
module.exports = {
FindinCol1 : function funk1(callback) {
MongoClient.connect("mongodb://localhost:27017/db1", function (err,db) {
if (err) {
return console.dir(err);
}
var collection = db.collection('col1');
collection.find().toArray(function (err, items) {
console.log(items);
return callback(items);
});
});
}
};
Pass a callback function to FindinCol1:
a.FindinCol1(function(items) {
console.log(items);
});
I suggest you to check this article:
https://docs.nodejitsu.com/articles/getting-started/control-flow/what-are-callbacks
I’m new to node and mongo after 15 years of VB6 and MySql. I’m sure this is not what my final program will use but I need to get a basic understanding of how to call a function in another module and get results back.
I want a module to have a function to open a DB, find in a collection and return the results. I may want to add a couple more functions in that module for other collections too. For now I need it as simple as possible, I can add error handlers, etc later. I been on this for days trying different methods, module.exports={… around the function and with out it, .send, return all with no luck. I understand it’s async so the program may have passed the display point before the data is there.
Here’s what I’ve tried with Mongo running a database of db1 with a collection of col1.
Db1.js
var MongoClient = require('mongodb').MongoClient;
module.exports = {
FindinCol1 : function funk1(req, res) {
MongoClient.connect("mongodb://localhost:27017/db1", function (err,db) {
if (err) {
return console.dir(err);
}
var collection = db.collection('col1');
collection.find().toArray(function (err, items) {
console.log(items);
// res.send(items);
}
);
});
}
};
app.js
a=require('./db1');
b=a.FindinCol1();
console.log(b);
Console.log(items) works when the 'FindinCol1' calls but not console.log(b)(returns 'undefined') so I'm not getting the return or I'm pasted it by the time is returns. I’ve read dozens of post and watched dozens of videos but I'm still stuck at this point. Any help would be greatly appreciated.
As mentioned in another answer, this code is asynchronous, you can't simply return the value you want down the chain of callbacks (nested functions). You need to expose some interface that lets you signal the calling code once you have the value desired (hence, calling them back, or callback).
There is a callback example provided in another answer, but there is an alternative option definitely worth exploring: promises.
Instead of a callback function you call with the desired results, the module returns a promise that can enter two states, fulfilled or rejected. The calling code waits for the promise to enter one of these two states, the appropriate function being called when it does. The module triggers the state change by resolveing or rejecting. Anyways, here is an example using promises:
Db1.js:
// db1.js
var MongoClient = require('mongodb').MongoClient;
/*
node.js has native support for promises in recent versions.
If you are using an older version there are several libraries available:
bluebird, rsvp, Q. I'll use rsvp here as I'm familiar with it.
*/
var Promise = require('rsvp').Promise;
module.exports = {
FindinCol1: function() {
return new Promise(function(resolve, reject) {
MongoClient.connect('mongodb://localhost:27017/db1', function(err, db) {
if (err) {
reject(err);
} else {
resolve(db);
}
}
}).then(function(db) {
return new Promise(function(resolve, reject) {
var collection = db.collection('col1');
collection.find().toArray(function(err, items) {
if (err) {
reject(err);
} else {
console.log(items);
resolve(items);
}
});
});
});
}
};
// app.js
var db = require('./db1');
db.FindinCol1().then(function(items) {
console.info('The promise was fulfilled with items!', items);
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
Now, more up to date versions of the node.js mongodb driver have native support for promises, you don't have to do any work to wrap callbacks in promises like above. This is a much better example if you are using an up to date driver:
// db1.js
var MongoClient = require('mongodb').MongoClient;
module.exports = {
FindinCol1: function() {
return MongoClient.connect('mongodb://localhost:27017/db1').then(function(db) {
var collection = db.collection('col1');
return collection.find().toArray();
}).then(function(items) {
console.log(items);
return items;
});
}
};
// app.js
var db = require('./db1');
db.FindinCol1().then(function(items) {
console.info('The promise was fulfilled with items!', items);
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
Promises provide an excellent method for asynchronous control flow, I highly recommend spending some time familiarizing yourself with them.
Yes, this is an async code and with a return you will get the MongoClient object or nothing, based on where you put.
You should use a callback parameter:
module.exports = {
FindinCol1 : function funk1(callback) {
MongoClient.connect("mongodb://localhost:27017/db1", function (err,db) {
if (err) {
return console.dir(err);
}
var collection = db.collection('col1');
collection.find().toArray(function (err, items) {
console.log(items);
return callback(items);
});
});
}
};
Pass a callback function to FindinCol1:
a.FindinCol1(function(items) {
console.log(items);
});
I suggest you to check this article:
https://docs.nodejitsu.com/articles/getting-started/control-flow/what-are-callbacks