Given the test codes below:
subAuthCall:
const sinon = require('sinon')
const sandbox = sinon.createSandbox()
function stubSuccessCall() {
return sandbox.stub(authorization, 'authorize').returns({enabled: true});
}
function stubFailedCall() {
return sandbox.stub(authorization, 'authorize').returns({enabled: false});
function restoreSub() {
sandbox.restore();
}
in the authorization.js file, I have:
function authorize(data) {
return data.enabled;
}
module.exports = {
authorize
}
and then in the middleware, I have:
const {authorize} = require('./authorization')
async function check() {
//import auth data
console.log(authorize(data))
if (authorize(data)) {
//resolve to true
} else {
//reject
}
}
Then in the test case, I called:
afterEach(authorizationStub.restorStub);
describe('auth testing', () => {
it('test successful', () => {
authorizationStub.stubSuccessCall();
return check().then(res => {expect(res.result).to.equl(true)});
})
it('test failed', () => {
authorizationStub.stubFailedCall();
return check().then(res => {expect(res.result).to.equl(false)});
})
})
It's a overly simplified auth logic and test case. The weird problem I had is that if I run both test cases, it prints out:
1 - test successful
true // from console.log(authorize(data))
2 - test failed
true // from console.log(authorize(data))
The 1st test case passed and the 2nd one failed (because the stubbing didn't return the right result)
but in the test failed, case, it supposed to return false as how I stub it in stubFailedCall, but it still has the same result in stubSuccessCall. I have verified the restoreStub is called.
I accidentally came across the fix: In the middleware, I used to have:
const {authorize} = require('./authorization')
...
but if I changed this to:
const auth = require('./authorization')
and then in the codes, instead of:
authorize(data)
I do:
auth.authorize(data)
This will work - both test cases will pass without any other changes.
My question is, why sinon stub/restoring stub didn't work if I deconstruct the authorize call in the middleware, but if I use an object to make the call, it will work? What's the mechanism behind this?
Related
I'm trying to write a unit test for a Node.js project's logic using Jest.
However, most documentations only provide a case for importing a module or class, however, in my case, my module only contains functions.
So far I know that there are mainly three ways to test a function in Jest:
1) jest.fn()
2) jest.spyOn
3) jest.mock('path')
I have tried all three but none work.
I wanted to test if the function returns a correct value(string) when called.
I tried many different
Here's my code: (I will show short snippets of my code in the later parts)
getDefApCode.ts
export function getDefApCode(code: string) {
switch (code) {
case 'BKK':
return 'NRT'
case 'CTX':
return 'ICN'
case 'SIN':
return 'TPE'
default:
return code
}
}
export function getDefaultDepartureCode(code: string) {
return code ? getDefaultLocationCode(code) : 'LHR'
}
export function getDefaultDestinationCode(code: string) {
return code ? getDefaultLocationCode(code) : 'ZRH'
}
getDefAPCode.spec.ts >> Pattern 1 (using required + jest.fn)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefAPCode from "../../../src/controllers/logic/getDefAPCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
it("should get a default location code", async () => {
const getLocation = require('../../../src/controllers/logic/getDefAPCode');
const code = jest.fn(code => 'BKK');
const getCode = getLocation(code);
expect(getCode).toHaveBeenCalled();
});
});
Error Message:
TypeError: getLocation is not a function
getDefAPCode.spec.ts >> Pattern 2 (using spyON)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefaultLocationCode from "../../../src/controllers/logic/getDefaultLocationCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
const { getDefaultLocationCode, getDefaultDepartureCode, getDefaultDestinationCode } = require('../../../src/controllers/logic/getDefaultLocationCode');
it("should get a default location code", async () => {
const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
getDefaultLocationCode.getDefaultLocationCode('AKJ');
expect(spy).toHaveBeenCalled();
});
});
These are some error messages appear when I tried a different pattern (I didn't keep track of all of the test code pattern, will add the test code pattern once I fixed docker)
Error Message:
Cannot spy the getDefaultLocationCode property because it is not a function; undefined given instead
31 | const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
Past Error Messages
error TS2349: This expression is not callable.
Type 'typeof import("/app/src/controllers/logic/getDefAPCode")' has no call signatures.
another one
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: string
Received has value: "NRT"
I figured out that I don't have to use mock function in this case.
I stored argument in a variable and then I use the variable instead using a string directly.
Here's how I edit my test code
it("should get a default location code", () => {
const code = 'BKK';
expect(code).toHaveBeenCalled();
});
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 got two problems with this jest test:
Is it possible to define the Content collection only once instead of doing that inside of the test?
I do get this error:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.
I don't see why my async code weren't stopped...
import resolvers from 'resolvers/'
import Db from 'lib/db'
const db = new Db()
describe('Resolver', () => {
let token
beforeAll(async () => {
await db.connect()
})
beforeEach(async () => {
token = 'string'
await db.dropDB()
})
afterAll(async () => {
await db.connection.close()
})
describe('articleGetContent()', () => {
test('should return dataset', async () => {
// SETUP
const Content = db.connection.collection('content')
const docs = [{
// some content...
}]
await Content.insertMany(docs)
// EXECUTE
const result = await resolvers.Query.articleGetContent({}, {
id: '123,
language: 'en'
}, {
token
})
// VERIFY
expect.assertions(1)
expect(result).toBeDefined()
})
})
})
resolver
import { articleGetContent } from '../models/article'
export default {
Query: {
articleGetContent: async (obj, { id }, { token }) => articleGetContent(id, token)
}
}
This is how my db class looks like
db.js
export default class Db {
constructor (uri, callback) {
const mongo = process.env.MONGO || 'mongodb://localhost:27017'
this.mongodb = process.env.MONGO_DB || 'testing'
this.gfs = null
this.connection = MongoClient.connect(mongo, { useNewUrlParser: true })
this.connected = false
return this
}
async connect (msg) {
if (!this.connected) {
try {
this.connection = await this.connection
this.connection = this.connection.db(this.mongodb)
this.gfs = new mongo.GridFSBucket(this.connection)
this.connected = true
} catch (err) {
console.error('mongo connection error', err)
}
}
return this
}
async disconnect () {
if (this.connected) {
try {
this.connection = await this.connection.close()
this.connected = false
} catch (err) {
console.error('mongo disconnection error', err)
}
}
}
async dropDB () {
const Content = this.connection.collection('content')
await Content.deleteMany({})
}
}
Related to the second question I hope you've found some issues on github about it.
In general, the issue is described in the debug log.
Jest works with promises, as a result, you shouldn't leave any async operations in any status except resolved.
In your case, you have your DB connection opened so you need to implement another method disconnect for your DB class, this link to docs will help you, but I guess you have it already as it's not the full db.js file ( I see some custom method dropDB. Main idea here is to have it in afterAll hook:
afterAll(() => db.disconnect());
Great example at the bottom of the page
What about the first question, it really depends on what you are doing in your method dropDB. If you're running method for dropping collection, you could store the reference to this collection somewhere outside and use it as it will automatically create the new one, but it would be great to see this method.
Additionally, your async test was created in a wrong way, you could read more here for example in my Update. You need to run this function in the beginning of the test: expect.assertions(number)
expect.assertions(number) verifies that a certain number of assertions
are called during a test. This is often useful when testing
asynchronous code, in order to make sure that assertions in a callback
actually got called.
using jest to unit test, I have the following line:
jest.mock('../../requestBuilder');
and in my folder, i have a
__mocks__
subfolder where my mock requestBuilder.js is. My jest unit test correctly calls my mock requestBuilder.js correctly. Issue is, my requestBuilder is mocking an ajax return, so I want to be able to determine if I should pass back either a successful or failure server response. Ideally I want to pass a parameter into my mock function to determine if "ajaxSuccess: true/false". How can I do this? Thank you
You don't want to pass a parameter into your mock function, the parameters that are passed to your mock function should be controlled by the piece of code that you are testing. What you want to do is change the mocking behavior between executions of the mock function.
Let's assume that you're trying to test this snippet of code:
// getStatus.js
const requestBuilder = require('./requestBuilder');
module.exports = () => {
try {
const req = requestBuilder('http://fake.com/status').build();
if (req.ajaxSuccess) {
return {status: 'success'};
} else {
return {status: 'failure'}
}
} catch (e) {
return {status: 'unknown'};
}
};
We want to test that getStatus uses the requestBuilder properly, not that the builder.build() method works correctly. Verifying builder.build() is the responsibility of a separate unit test. So we create a mock for our requestBuilder as follows:
// __mocks__/requestBuilder.js
module.exports = jest.fn();
This mock simply sets up the mock function, but it does not implement the behavior. The behavior of the mock should defined in the test. This will give you find grained control of the mocking behavior on a test-by-test basis, rather than attempting to implement a mock that supports every use case (e.g. some special parameter that controls the mocking behavior).
Let's implement some tests using this new mock:
// getStatus.spec.js
jest.mock('./requestBuilder');
const requestBuilder = require('./requestBuilder');
const getStatus = require('./getStatus');
describe('get status', () => {
// Set up a mock builder before each test is run
let builder;
beforeEach(() => {
builder = {
addParam: jest.fn(),
build: jest.fn()
};
requestBuilder.mockReturnValue(builder);
});
// every code path for get status calls request builder with a hard coded URL,
// lets create an assertion for this method call that runs after each test execution.
afterEach(() => {
expect(requestBuilder).toHaveBeenCalledWith('http://fake.com/status');
});
it('when request builder creation throws error', () => {
// Override the mocking behavior to throw an error
requestBuilder.mockImplementation(() => {
throw new Error('create error')
});
expect(getStatus()).toEqual({status: 'unknown'});
expect(builder.build).not.toHaveBeenCalled();
});
it('when build throws an error', () => {
// Set the mocking behavior to throw an error
builder.build.mockImplementation(() => {
throw new Error('build error')
});
expect(getStatus()).toEqual({status: 'unknown'});
expect(builder.build).toHaveBeenCalled();
});
it('when request builder returns success', () => {
// Set the mocking behavior to return ajaxSuccess value
builder.build.mockReturnValue({ajaxSuccess: true});
expect(getStatus()).toEqual({status: 'success'});
expect(builder.build).toHaveBeenCalled();
});
it('when request builder returns failure', () => {
// Set the mocking behavior to return ajaxSuccess value
builder.build.mockReturnValue({ajaxSuccess: false});
expect(getStatus()).toEqual({status: 'failure'});
expect(builder.build).toHaveBeenCalled();
});
});
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).