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()
Related
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 suite of tests but something is just not clicking regarding callback assertions. My feeling is that the done() parameter needs to be woven in, but I'm doing it wrong.
I basically have two function structures, where the callback is either nested inside of a single then statements, or a then inside of another then:
function foo(cb) {
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
.catch(err => console.error(err))
}
and
function foo(cb) {
return fetch('foo.com')
.then(()=>cb())
.catch(err => console.error(err))
}
I'm looking to assert that the callback was called in both cases.
I have tried
describe('ugh why can't I figure this out'?, () => {
it('is confusing', (done) => {
const testCb = jest.fn()
foo(testCb);
expect(testCb).toHaveBeenCalled()
done();
//failed: expected testCb to be called, but it was not called
}
})
I'm not sure how to move forward- I'm not a fan of the spaghetti on the wall approach, so I'd rather understand how to implement jest for testing async code before I just start switching in different syntax. The above code seems like it should work because the callback is part of the function execution, so if the higher order function executes, the callback would necessarily be executed, and should have been "called". Obviously this reasoning is wrong since the test isn't passing, but I'm not quite seeing the way around this.
Thanks very much for any direction/insight :).
This jest example seems to match mine- what am I missing?
describe('drinkAll', () => {
test('drinks something lemon-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'lemon');
expect(drink).toHaveBeenCalled();
});
test('does not drink something octopus-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'octopus');
expect(drink).not.toHaveBeenCalled();
});
});
You're expect is being called before the fetch comes back. Do this:
foo(testCb)
.then(_ => {
expect(testCb).toHaveBeenCalled();
done();
})
.catch(err => {
done.fail(err);
});
By chaining on to the Promise returned by foo we ensure the fetch has come back. Once you go async you have to stay that way, you can't mix sync and async code like you did in your posted code:
const testCb = jest.fn()
foo(testCb); // this can take an arbitrary amt of time
expect(testCb).toHaveBeenCalled() // but this happens immediately
done();
FWIW you can also change this
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
Into this:
return fetch('foo.com')
.then((res)=> res.json())
.then(cb)
.catch((err) => ...
The extra level of nesting promises is unnecessary and makes the code hard to read.
I have a API script in a file
const ApiCall = {
fetchData: async (url) => {
const result = await fetch(url);
if (!result.ok) {
const body = await result.text(); // uncovered line
throw new Error(`Error fetching ${url}: ${result.status} ${result.statusText} - ${body}`); // uncovered line
}
return result.json();
},
};
export default ApiCall;
When I mock the call, I have two uncovered lines in code coverage.
Any idea how can I make them cover as well.
Here is what I have tried so far which is not working
it('test', async () => {
ApiCall.fetchData = jest.fn();
ApiCall.fetchData.result = { ok: false };
});
I am kind of new into Jest, so any help would be great.
You need to provide a stubb response in your test spec so that the if statement is triggered. https://www.npmjs.com/package/jest-fetch-mock will allow you to do just that. The example on their npm page should give you what you need https://www.npmjs.com/package/jest-fetch-mock#example-1---mocking-all-fetches
Basically the result is stored in state(redux) and is called from there. jest-fetch-mock overrides your api call/route and returns the stored result in redux all within the framework.
Assuming that what you want to test is the ApiCall then you would need to mock fetch. You are mocking the entire ApiCall so those lines will never execute.
Also, you have an issue, because if you find an error or promise rejection, the json() won't be available so that line will trigger an error.
Try this (haven't test it):
it('test error', (done) => {
let promise = Promise.reject(new Error("test"));
global.fetch = jest.fn(() => promise); //You might need to store the original fetch before swapping this
ApiCall.fetchData()
.catch(err => );
expect(err.message).toEqual("test");
done();
});
it('test OK', (done) => {
let promise = Promise.resolve({
json: jest.fn(() => {data: "data"})
});
global.fetch = jest.fn(() => promise);
ApiCall.fetchData()
.then(response => );
expect(response.data).toEqual("data");
done();
});
That probably won't work right away but hopefully you will get the idea. In this case, you already are working with a promise so see that I added the done() callback in the test, so you can tell jest you finished processing. There is another way to also make jest wait for the promise which is something like "return promise.then()".
Plese post back
Using proxyquire, sinon, and mocha.
I am able to stub fetch on the first call of fetch. But on the second fetch call, which is recursive, I am not able to assert it. From the output, it looks like the assertion may run before the test finishes. You will see this with second fetch console out after assertion.
index.js
var fetch = require('node-fetch');
function a() {
console.log('function a runs');
fetch('https://www.google.com')
.then((e) => {
console.log('first fetch');
b();
})
.catch((e)=> {
console.log('error')
});
}
function b() {
fetch('https://www.google.com')
.then((e) => {
console.log('second fetch');
})
.catch((e)=> {
console.log('error')
});
}
a()
test:
describe('fetch test demo', ()=> {
it('fetch should of called twice', (done)=> {
fetchStub = sinon.stub();
fetchStub2 = sinon.stub();
fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [ fetchStub, fetchStub2 ]
fetch.returns(Promise.all(promises));
proxy('../index', {
'node-fetch': fetch
});
fetch.should.have.been.callCount(2);
done()
});
});
fetch test demo
function a runs
1) fetch should of called twice
first fetch
second fetch
lifx alert test
- fetch should of called three times
when rain change is over 50%
- should run fetch twice
0 passing (78ms)
2 pending
1 failing
1) fetch test demo fetch should of called twice:
expected stub to have been called exactly twice, but it was called once
stub(https://www.google.com) => [Promise] { } at a (/home/one/github/lifx-weather/foobar.js:5:3)
AssertionError: expected stub to have been called exactly twice, but it was called once
stub(https://www.google.com) => [Promise] { } at a (foobar.js:5:3)
at Context.it (test/bar.js:22:28)
Updated version
#dman, since you updated your test case I owe you an updated answer. Although rephrased, the scenario is still unorthodox - it seems like you want to ignore in a sense the 'law of gravity' even though you know it's right there in front of you.
I'll try to be as descriptive as possible. You have two functions which are doing async stuff by design. a() calls b() sequentially - by the way this is not recursion. Both functions do not notify their callers upon completion / failure, i.e. they are treated as fire-and-forget.
Now, let's have a look at your test scenario. You create 3 stubs. Two of them resolve to a string and one combining their execution using Promise.all(). Next, you proxy the 'node-fetch' module
proxy('./updated', {
'node-fetch': fetch
});
using the stub that returns the combined execution of stubs 1 & 2. Now, if you print out the resolved value of fetch in either function, you will see that instead of a string it's an array of stubs.
function a () {
console.log('function a runs');
fetch('http://localhost')
.then((e) => {
console.log('first fetch', e);
b();
})
.catch((e) => {
console.log('error');
});
}
Which I guess is not the intended output. But let's move over as this is not killing your test anyway. Next, you have added the assertion together with the done() statement.
fetch.should.have.been.callCount(2);
done();
The issue here is that whether you are using done() or not, the effect would be exactly the same. You are executing your scenario in sync mode. Of course in this case, the assertion will always fail. But the important thing here is to understand why.
So, let's rewrite your scenario to mimic the async nature of the behavior you want to validate.
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
chai.use(SinonChai);
chai.should();
const proxy = require('proxyquire');
describe('fetch test demo', () => {
it('fetch should of called twice', (done) => {
var fetchStub = sinon.stub();
var fetchStub2 = sinon.stub();
var fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [fetchStub, fetchStub2];
fetch.returns(Promise.all(promises));
proxy('./updated', {
'node-fetch': fetch
});
setTimeout(() => {
fetch.should.have.been.callCount(2);
done();
}, 10);
});
});
As you can see, the only change made was wrapping the assertion within a timer block. Nothing much - just wait for 10ms and then assert. Now the test passes as expected. Why?
Well, to me it's pretty straightforward. You want to test 2 sequentially executed async functions and still run your assertions in sync mode. That sounds cool, but it's not gonna happen :) So you have 2 options:
Have your functions notify callers upon completion and then run your assertions in truly async mode
Mimic the async nature of things using unorthodox techniques
Reply based on original test scenario
It can be done. I've re-factored your provided files a bit so that
can be executed.
index.js
const fetch = require('node-fetch');
const sendAlert = require('./alerts').sendAlert;
module.exports.init = function () {
return new Promise((resolve, reject) => {
fetch('https://localhost')
.then(function () {
sendAlert().then(() => {
resolve();
}).catch(
e => reject(e)
);
})
.catch(e => {
reject(e);
});
});
};
alerts.js
const fetch = require('node-fetch');
module.exports.sendAlert = function () {
return new Promise((resolve, reject) => {
fetch('https://localhost')
.then(function () {
resolve();
}).catch((e) => {
reject(e);
});
});
};
test.js
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
chai.use(SinonChai);
chai.should();
const proxy = require('proxyquire');
describe.only('lifx alert test', () => {
it('fetch should of called twice', (done) => {
var body = {
'hourly': {
data: [{
time: 1493413200,
icon: 'clear-day',
precipIntensity: 0,
precipProbability: 0,
ozone: 297.17
}]
}
};
var response = {
json: () => {
return body;
}
};
const fetchStub = sinon.stub();
fetchStub.returns(Promise.resolve(response));
fetchStub['#global'] = true;
var stubs = {
'node-fetch': fetchStub
};
const p1 = proxy('./index', stubs);
p1.init().then(() => {
try {
fetchStub.should.have.been.calledTwice;
done();
} catch (e) {
done(e);
}
}).catch((e) => done(e));
});
});
What you're trying to do though is a bit unorthodox when it comes to
good unit testing practices. Although proxyquire supports this
mode of stubbing through a feature called global overrides, it is
explained here why should anyone think twice before going down
this path.
In order to make your example pass the test, you just need to add an
extra attribute to the Sinon stub called #global and set it to
true. This flag overrides the require() caching mechanism and
uses the provided stub no matter which module is called from.
So, although what you're asking can be done I will have to agree with
the users that commented your question, that this should not be
adopted as a proper way of structuring your tests.
Here is also a alternative way to do this using Promise.all().
Note: this won't work if using fetch's json method and you need to pass data in the resolve() for logic on data. It will only pass in the stubs when resolved. However, it will assert the number of times called.
describe('fetch test demo', () => {
it('fetch should of called twice', () => {
let fetchStub = sinon.stub();
let fetchStub2 = sinon.stub();
let fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [ fetchStub, fetchStub2 ]
var promise = Promise.all(promises);
fetch.returns(promise);
proxy('../foobar', { 'node-fetch': fetch });
return promise.then(() => {
fetch.should.have.callCount(2);
});
});
});
I have found another way to get things done.
May be this could work for someone.
describe('Parent', () => {
let array: any = [];
before(async () => {
array = await someAsyncDataFetchFunction();
asyncTests();
});
it('Dummy test to run before()',async () => {
expect(0).to.equal(0); // You can use this test to getting confirm whether data fetch is completed or not.
});
function asyncTests() {
array.forEach((currentValue: any) => {
describe('Child', async () => {
it('Test '+ currentValue ,() => {
expect(currentValue).to.equal(true);
})
})
});
}
});
That's how I achieved the assertion on every element of the array. (Array data is being fetch asynchronously).
New to Mocha unit testing, I have a few Mocha examples that are running fine, but I have been trying for hours to get this one to run and no matter what I do, after() is called way earlier than I feel it should. Here's an example:
var dummyData = require('./dummyData.js')
describe('mochaTest', function() {
after(function(done) {
dummyData.cleanDb(function(){
done();
})
});
it('should hit the db to get dummy data and send', function(done) {
dummyData.createDummyData(function(data1, data2, Lookup) {
Lookup.lookup({
data1: data1,
data2: data2
}, function(err, result) {
done();
});
});
});
})
And then in dummyData.js:
exports.createDummyData = function(cb){
doSomeStuff(function (err, patient) {
// Connect to db, get some data to pass.
var Lookup = require(./Lookup.js);
cb(data1, data2, Lookup);
})
}
exports.cleanDb = function(cb) {
// Clear db connections.
cb();
}
The problem is that right after the test is run, the after() function gets called and the Lookup function can't hit the db, because the db connection has been cleared. Why is after being called so early, it shouldn't be called until the it statement calls done() right?
This is an old question, but I have experienced the same issue and could not find any explanation to solve this. Unfortunately I do not have enough reputation to answer as a comment, so I'll share how I solved my issue here.
In my controller, I had a method outlined as follows:
exports.insert = (request, response) => {
UserModel.createUser(request.body)
.then(() => {
respond.status(201).send({message: 'User created successfully
});
};
I realized the issue here, was that my test method wasn't waiting for a response from my User.insert(), because this function is void -- it does not return a value. So the following test would jump straight to calling done(); since there was no reason to wait for a response from User.insert(). This was causing my after hook to run prematurely and close the connection to my database before my UserModel could populate.
// Code is shortened, but my request and response was formed using node-mocks-http package
it('should return status 201', function (done) {
User.insert(request, response);
assert(response._getStatusCode() === 201);
done();
};
I hate to say how long this took me to realize this. But, I needed to return a promise from my controller, so that my test function would have something to wait on. By changing my controller to this:
exports.insert = async (request, response) => {
await UserModel.createUser(request.body)
.then(() => {
response.status(201).send({message: "User created successfully"});
})
.catch(() => {
response.status(400).send({message: "There is already an account with that email"});
});
return response;
And with my test looking something like this:
it('should return a status code of 201', async function () {
await User.insert(request, response);
assert(response._getStatusCode() === 201);
});
The database populates successfully, and my test will wait for my promise to fulfill, thus updating the response before calling my assert. Then the after hook works when expected.
I hope this helps someone else who stumbles across this old thread.