Mock and assert a function call in a promise - javascript

I'm writing some tests for a simple node.js module which returns the result of another function, currently that module looks like this:
const doAPostRequest = require('./doAPostRequest.js').doAPostRequest
const normalizeResponse = require('./normalizeResponse.js').normalizeResponse
const errorObject = require('./responseObjects.js').errorObject
const successObject = require('./responseObjects.js').successObject
exports.handlePost = (postData) => {
return doAPostRequest(postData).then((res) => {
const normalizedResponse = normalizeResponse(res);
return successObject(normalizedResponse);
}).catch((error) => {
const { statusCode, message } = error;
return errorObject(statusCode, message);
});
}
doAPostRequest is another module that returns a promise. Now i want to test for the following cases:
if doAPostRequest is being called once with the params given to handlePost.
is normalizeResponse is being called once with the params that are returned from doAPostRequest.
is successObject is being called once with the params that are returned from normalizedResponse.
is errorObject is being called once with the params that are returned from doAPostRequest (if there is an error).
But i can't figure out how i can test if the functions are called within the promise and if doAPostRequest is being called in the first place.
Currently i have the following test suite for the module written with the help of the Chai, Sinon and Sinon-chai libraries.
const sinon = require('sinon');
const chai = require('chai');
const handlePost = require('./handlePost.js').handlePost
const params = { body: 'title': 'foo' }
chai.use(require('sinon-chai'));
it('Calls doAPostRequest', () => {
doAPostRequest = sinon.stub().resolves("Item inserted")
handlePost(params)
expect(doAPostRequest).to.have.been.calledOnce;
expect(doAPostRequest).to.have.been.calledWith(params);
});
it('Calls normalizeResponse', () => {
normalizeResponse = sinon.stub().resolves("result")
return handlePost(params).then((res) => {
expect(normalizeResponse).to.have.been.calledOnce;
expect(normalizeResponse).to.have.been.calledWith("Item inserted");
});
});
However the tests fail with the following error response:
AssertionError: expected stub to have been called exactly once, but it was called 0 times
Maybe i'm overlooking something and is this not the right way to test this piece of code? I found this topic and the problem is close to identical, however for me non of the functions are getting called apparently. This leads to me thinking that i must overlook something. Any help and/or suggestions will be appreciated.

Related

How to mock a method that accepts no arguments and its supposed to work normally in 1 test, and supposed to throw an error in another test

I'm trying to get 100% coverage on my AWS project but I don't know how to mock methods that don't accept arguments AND are supposed to pass a test that makes them work properly(return values) and another test that makes them throw an error. I can't change the tech I am using so please try to help me with the things I am using right now.
I am using Nodejs, Typescript, Mocha, Chai, nyc and mock-require for mocking.
It's an AWS project so I am working with AWS methods
Here is the function and method, I am mocking describeAutoScalingGroups()
export async function suspendASGroups() {
const autoscaling = new AWS.AutoScaling();
const asgGroups = await autoscaling.describeAutoScalingGroups().promise();
if (!asgGroups.AutoScalingGroups) {
throw new Error("describeAutoScalingGroups inside of suspendAGSGroups didn't return any groups");
}
// some other stuff below
This is the TEST that is supposed to fail(Above this there is a test of the same function which will return regular values)
it('Should throw an error, should fail', async () => {
assertNative.rejects(awsFunctions.resumeAGSGroups());
try {
let result = await awsFunctions.suspendASGroups();
} catch (e) {
assert.isTrue(
e.name == 'Error' &&
e.message == "describeAutoScalingGroups inside of suspendAGSGroups didn't return any groups",
'describeAutoScalingGroups in suspendAGSGroups didnt have the proper error message'
);
}
});
And here is the mock code
public describeAutoScalingGroups() {
const data = (): AWS.AutoScaling.Types.AutoScalingGroupsType => {
return {
// some values here
};
return {
promise: data
};
}
I expect to be able to pass both tests, the one that expects a regular value and one that expects it to throw an error
here is a picture of the coverage: https://i.imgur.com/D6GX0tf.png
I expect that red part to be gone :)
Thank you
In your mock you need to return something for AutoScalingGroupsType that evaluates to false, since you have this check:
if (!asgGroups.AutoScalingGroups) { ... }
So you could simply do this:
public describeAutoScalingGroups() {
return {
promise: () => {
return { AutoScalingGroups: false }
}
};
I was given an answer on reddit so I will post it here too:
You need a different mock for each test. You should setup your mocks in before/beforeEach hooks which you will get from mocha https://mochajs.org/#hooks.
Using sinon would make the mock creation cleaner but if you are stuck with mock-require then it looks like you will need to use https://www.npmjs.com/package/mock-require#mockrerequirepath
--- end of comment
How I did it:
I made another mock file that is the same as the regular mock file, except this one can only fail functions(which is good)
Here is the code in the TEST:
describe('Testing FAILING suspendAGSGroups', () => {
it('Should throw an error, should fail', async () => {
const mock = require('mock-require');
mock.stopAll();
let test = require('./test/mocks/indexFailMocks');
mock.reRequire('./test/mocks/indexFailMocks');
let awsFailing = mock.reRequire('./handler');
// the line above is pretty important, without it It wouldnt have worked, you need to reRequire something even if it's the code of it isn't changed(I only changed the MOCK file but I had to reRequire my main function)
try {
let result = await awsFailing.suspendASGroups();
} catch (e) {
assert.isTrue(
e.name == 'Error' &&
e.message == "describeAutoScalingGroups inside of
suspendAGSGroups didn't return any groups",
'describeAutoScalingGroups in suspendAGSGroups didnt have the proper
error message'
);
}
});
});

Sinon: Stubbing static method of class does not work as expected

I want to mock the following class, that is used as dependency for another one:
module.exports = class ResponseHandler {
static response(res, status_, data_, codes_) {
console.log('Im here. Unwillingly');
// doing stuff here...
};
The ResponseHandler is imported by the ProfileController and used there:
const response = require('../functions/tools/response.js').response;
module.exports = class ProfileController {
static async activateAccountByVerificationCode(req, res) {
try{
// doing stuff here
return response(res, status.ERROR, null, errorCodes);
}
}
Now I'm writing Unit Tests for the ProfileController and there I'm testing if activateAccountByVerificationCode calls response with the given arguments
describe('ProfileController', () => {
let responseStub;
beforeEach(function() {
responseStub = sinon.stub(ResponseHandler, 'response').callsFake(() => null);
});
But despite the fact that response is mocked, the ProfileController still calls the real implementation of response (See console output: 'Im here. Unwillingly')
it('should respond accordingly if real verification code does not fit with the one passed by the user', async function () {
// here you can still see that real implementation is still called
// because of console output 'I'm here unwillingly'
await controller.activateAccountByVerificationCode(req, res);
console.log(responseStub.called); // -> false
expect(responseStub.calledWith(res, status.ERROR, null, [codes.INVALID_VERIFICATION_CODE])).to.eql(true); // -> negative
});
You need to mock you controllers dependecies with a library like proxyquire first and then use this mocked instance in you test. Otherwise you will still use the original (unstubbed) implementation.
const proxyquire = require('proxyquire');
describe('ProfileController', () => {
let responseStub;
let Controller;
beforeEach(function() {
responseStub = sinon.stub(ResponseHandler, 'response').callsFake(() => null);
Controller = proxyquire('./ProfileController', {'../functions/tools/response':responseStub})
});
it('should respond accordingly if real verification code does not fit with the one passed by the user', async function () {
// here you can still see that real implementation is still called
// because of console output 'I'm here unwillingly'
await Controller.activateAccountByVerificationCode(req, res);
console.log(responseStub.called); // -> false
expect(responseStub.calledWith(res, status.ERROR, null, [codes.INVALID_VERIFICATION_CODE])).to.eql(true); // -> negative
});
Controller then uses your stubbed version of the function and can be inspected.

Testing console output (process.stdout.write) from async functions using Mocha

I'm having issues capturing process.stdout.write within an async function in node.js. I've read a lot of other people's solutions, and am missing something obvious, but I can't figure out what it is. I found solutions here that worked for the sync functions, but can't make the async thing work. I've tried both homegrown solutions, as well as the test-console.js library.
Here's the function I'm trying to test:
const ora = require('ora')
const coinInserted = (totalInserted) => {
const spinner = ora(' KA-CHUNK').start();
const output = `Amount Inserted: $${(totalInserted / 100).toFixed(2)}`;
setTimeout(() => {
spinner.text = ` ${output}`;
spinner.color = 'green';
spinner.succeed();
process.stdout.write('Please Insert Coins > ');
}, 500);
};
The docs in the test-console.js library say to test an async function like so:
var inspect = stdout.inspect();
functionUnderTest(function() {
inspect.restore();
assert.deepEqual(inspect.output, [ "foo\n" ]);
});
...but I don't understand the syntax here of functionUnderTest. I take it I have to modify the function I'm testing to accept a callback function, inside of which I'll call the test (inspect and assert) functions? But that doesn't seem to work either.
Since you use setTimeout(), we can use sinon.useFakeTimers to emulate the timeout.
Here is the example
const chai = require('chai');
const assert = chai.assert;
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const succeedStub = sinon.stub(); // try to make the expectation this method is called
const index = proxyquire('./src', {
'ora': (input) => ({ // try to mock `ora` package
start: () => ({
text: '',
color: '',
succeed: succeedStub
})
})
})
describe('some request test', function() {
it('responses with success message', function() {
const clock = sinon.useFakeTimers(); // define this to emulate setTimeout()
index.coinInserted(3);
clock.tick(501); // number must be bigger than setTimeout in source file
assert(succeedStub.calledOnce); // expect that `spinner.succeed()` is called
});
})
Ref:
https://sinonjs.org/releases/latest/fake-timers/

How would I test this promise based code with jest?

How would I test this code in jest? I'd like to make sure that the error and success of the passed promise is being called as needed. I'm sure it's something sorta simple, but it's driving me crazy. Thanks very much.
handleStatusChangeRequest (changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
If your code uses promises, there is a nice way to handle asynchronous tests. Just return a promise from your test, and Jest will wait for that promise to resolve.
If the promise is rejected, the test will automatically fail.
For example, let's say that changeData, instead of using a callback, returns a promise that is supposed to resolve to the string "status has been successfully modified".
Be sure to return the promise - if you omit this return statement, your test will complete before your changeData() -[async function] completes.
Here's a convenient and easy to follow pattern
test('if the data is changed', () => {
return changeData().then((data) => {
expect(data).toBe('status has been successfully modified');
});
})
Happy testing :)
This could be refactored, but for the sake of demonstration, I left the repeating bits in.
In example.spec.js, the callback, changeEntryStatus, is stubbed to return a promise. In order to check if other instance methods (this.method) were called, they are first mocked, then assertions are called on the mock after running the method being tested. Learn more in the Jest docs. (See my thoughts on mocking methods of the unit being tested at the bottom.)
Run the example on repl.it.
example.js:
class Example {
handleStatusChangeRequest(changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
handleStatusChangeSuccess() {
console.log('stubbed handleStatusChangeSuccess')
}
handleErrorDisplay(error) {
console.log('stubbed handleErrorDisplay:', error)
}
}
module.exports = Example;
example.spec.js:
const Example = require('./entryStatus')
describe('handleStatusChangeRequest', () => {
it('should run the changeEntryStatus callback', () => {
const {handleStatusChangeRequest} = new Example()
const stub = jest.fn().mockResolvedValue()
handleStatusChangeRequest(stub)
// must return because handleStatusChangeRequest is asynchronous
return expect(stub).toHaveBeenCalled()
});
it('should call example.handleStatusChangeSuccess', async () => {
const example = new Example()
const stub = jest.fn().mockResolvedValue()
example.handleStatusChangeSuccess = jest.fn()
await example.handleStatusChangeRequest(stub)
expect(example.handleStatusChangeSuccess).toHaveBeenCalled();
})
it('should call example.handleErrorDisplay', async () => {
const example = new Example()
const fakeError = { code: 'fake_error_code' }
const stub = jest.fn().mockRejectedValue(fakeError)
example.handleErrorDisplay = jest.fn()
await example.handleStatusChangeRequest(stub)
expect(example.handleErrorDisplay).toHaveBeenCalled()
expect(example.handleErrorDisplay).toHaveBeenCalledWith(fakeError)
});
});
Opinionated Disclaimer: Mocking methods of the unit under test is a smell. Consider checking for the expected effects of calling handleStatusChangeSuccess and handleErrorDisplay instead of checking to see if they were called. Then don't even expose those methods publicly unless consumers of the class need access.
Opinionated Disclaimer: Mocking methods of the unit under test is a
smell. Consider checking for the expected effects of calling
handleStatusChangeSuccess and handleErrorDisplay instead of checking
to see if they were called. Then don't even expose those methods
publicly unless consumers of the class need access.
I wholeheartedly agree with webprojohn's disclaimer. Mocks are a smell as tests should assert the behavior of the code, not its implementation. Testing the latter makes the code brittle to change.
Stepping off my soapbox... :) We're looking for a way to test an asynchronous method. I'm not sure what assertions your tests should make to verify the behavior inside handleStatusChangeSuccess() and handleErrorDisplay(err) so the example below leaves a comment where those assertions would go. The following uses Promise.resolve() and Promise.reject() to trigger the outcomes to test. I've used async/await, Jest has other async examples in their docs.
const Example = require('./example')
describe('handleStatusChangeRequest', () => {
it('should resolve successfully', async () => {
const {handleStatusChangeRequest} = new Example();
const resolvePromise = () => Promise.resolve();
await handleStatusChangeRequest(resolvePromise);
// resolution assertions here
});
it('should resolve errors', async () => {
const {handleStatusChangeRequest} = new Example();
const fakeError = new Error('eep');
const rejectPromise = () => Promise.reject(fakeError);
// if your method doesn't throw, we can remove this try/catch
// block and the fail() polyfill
try {
await example.handleStatusChangeRequest(rejectPromise);
// if we don't throw our test shouldn't get here, so we
// polyfill a fail() method since Jest doesn't give us one.
// See https://github.com/facebook/jest/issues/2129
expect(true).toBe(false);
}
catch (e) {
// rejection assertions here
}
});
});
The answer I have looks so:
**Success tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleStatusChangeSuccess')
await instance.handleStatusChangeRequest(() => Promise.resolve('cool man'))
expect(spy).toHaveBeenCalledTimes(1)
**Error tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleErrorDisplay')
await instance.handleStatusChangeRequest(() => Promise.reject(Error('shit')))
expect(spy).toHaveBeenCalledTimes(1)
As I stated above, the handleStatusChangeSuccess and handleError methods are test else where with some snapshots (they just set state and render out some different jsx). I feel pretty good about this. I'm using spys/mocks, but I'm testing the implementation functions elsewhere. Sufficient?

Sinon Spy not being called with right arguments

Background
I am trying to learn how to do a RESTful API following the TDD paradigm by reading a book on the subject (it is in brazillian):
https://leanpub.com/construindo-apis-testaveis-com-nodejs/read
The author encourages the use a sinon.js together with mocha.js.
I am getting close to the end, but I am failling to pass the test for my gnomeController.
Problem
The problem is that I am using sinon to assert that I am calling the gnomeController's get method using the given reponse object, which is in reallity a spy.
This spy is to make sure I call the reponse method with an "Error", but it appears I am calling the response with no arguments whatsoever, which is very confusing.
Code
gnomeController.js
module.exports = aGnomeModel => {
let Gnome = aGnomeModel;
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err));
}
return Object.freeze({
get
});
};
gnomeTest.js
const sinon = require("sinon");
const gnomesControllerFactory = require("gnomesController.js");
const Gnome = require("gnomeModel.js");
describe("Controllers: Gnomes", () => {
describe("get() gnomes", () => {
it("should return 400 when an error occurs", () => {
const request = {};
const response = {
send: sinon.spy(),
status: sinon.stub()
};
response.status.withArgs(400).returns(response);
Gnome.find = sinon.stub();
Gnome.find.withArgs({}).rejects("Error");
const gnomesController = gnomesControllerFactory(Gnome);
return gnomesController.get(request, response)
.then(arg => {
console.log(arg);
sinon.assert.calledWith(response.send, "Error");
});
});
});
});
Question
I am using the latest versions of both libraries.
What is wrong in my code, why is the reponse being called with no arguments?
Solution
After much debugging, I found out that the solution is to replace:
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err));
}
with:
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err.name));
}
Which is not explained in the book. Kinda wish I could give more feedback on it, but so far it is what it is.

Categories

Resources