Description
I try to set stub fakes for a express middleware function and it's not replacing over.
What I'm trying (how to reproduce)
I'm trying to use sinon stubbing via callsFake function, just as it's advised from their most updated docs.
Even though I'm requiring the module and replacing the function from the property at the export. I keep seeing the original function behavior acting.
I know that I should try to get the function stubbed before the middleware functions get setup, and that's when express app is first imported.
This is the function I'm trying to stub, defined as a function and exported as a object too. It's defined in a script file with a path like api/middlewares/stripe/signature.
const stripeHelper = require('../../../lib/stripe')
const logger = require('../../../lib/logger')
const verifySignature = (req, res, next) => {
var event
let eventName = req.url.replace('/', '')
try {
// Try adding the Event as `request.event`
event = stripeHelper.signatureCheck(
eventName,
req.body,
req.headers['stripe-signature']
)
} catch (e) {
// If `constructEvent` throws an error, respond with the message and return.
logger.error('Error while verifying webhook request signature', e.message, e)
return res.status(400).send('Webhook Error:' + e.message)
}
req.event = event
next()
}
module.exports.verifySignature = verifySignature
What I tried already
Use decache to make sure the express app instance is pristine and it's not being initialized with previous original middleware
Set multiple beforEach hooks in order to organize my stubs and preconditions or test
What keeps happening
The original middleware function gets executed
I don't see any logs of the stub functions (as second proof that sinon stub is not working
This is my stubs and test hooks setup:
const chai = require('chai')
const chaiHttp = require('chai-http')
const dirtyChai = require('dirty-chai')
const sinon = require('sinon')
const decache = require('decache')
const signatureMiddleware = require('../../../api/middlewares/stripe/signature')
const bp = require('body-parser')
let verifySignatureStub, rawStub
chai.should()
chai.use(dirtyChai)
chai.use(chaiHttp)
const API_BASE = '/api/subscriptions'
const planId = 'NYA-RUST-MONTHLY'
const utils = require('../../utils')
const {
hooks: {createSubscription, emitPaymentSucceeded},
stripe: {generateEventFromMock}
} = utils
let testUser, testToken, testSubscription, server
describe.only('Subscriptions renewal (invoice.payment_succeeded)', function () {
this.timeout(30000)
beforeEach(function (done) {
createSubscription(server, {planId}, function (err, resp) {
if (err) return done(err)
const {user, jwt, subscription} = resp
console.log(user, jwt)
testUser = user
testToken = jwt
testSubscription = subscription
done()
})
})
beforeEach(function (done) {
verifySignatureStub = sinon.stub(signatureMiddleware, 'verifySignature')
rawStub = sinon.stub(bp, 'raw')
rawStub.callsFake(function (req, res, next) {
console.log('bp raw')
return next()
})
verifySignatureStub.callsFake(function (req, res, next) {
const {customerId} = testUser.stripe
const subscriptionId = testSubscription.id
console.log('fake verify')
req.event = generateEventFromMock('invoice.payment_failed', {subscriptionId, customerId, planId})
return next()
})
done()
})
beforeEach(function (done) {
decache('../../../index')
server = require('../../../index')
const {customerId} = testUser.stripe
const {id: subscriptionId} = testSubscription
console.log(`emitting payment succeeded with ${customerId}, ${subscriptionId} ${planId}`)
emitPaymentSucceeded(server, testToken, function (err, response) {
if (err) return done(err)
done()
})
})
afterEach(function (done) {
verifySignatureStub.restore()
done()
})
it('Date subscription will renew gets set to a valid number roughly one month', function () {
// Not even getting here becasue calling the original function contains verifyMiddleware which should be replaced
})
it('Current period end is modified')
it('An invoice for the new starting period is generated')
it('Subscription status keeps active')
})
Context (please complete the following information):
All runs over Node 8 and I'm running tests with mocha and did a set up with dirty chai.
These are my dev dependencies:
"devDependencies": {
"base64url": "^2.0.0",
"cross-env": "^5.0.5",
"decache": "^4.4.0",
"dirty-chai": "^2.0.1",
"faker": "^4.1.0",
"google-auth-library": "^0.12.0",
"googleapis": "^23.0.0",
"minimist": "^1.2.0",
"mocha": "^5.2.0",
"nodemon": "^1.12.0",
"nyc": "^11.2.1",
"sinon": "^6.1.5",
"standard": "^10.0.3",
"stripe-local": "^0.1.1"
}
Open issue
https://github.com/sinonjs/sinon/issues/1889
As a rule of thumb, stubs should be set up per test, i.e. in beforeEach or it, not in before. Here they don't seem to contain per-test logic but they can, and in this case they won't work as expected with before. mocha-sinon should preferably be used to integrate Mocha with Sinon sandbox, so no afterEach is needed to restore stubs, this is done automatically.
Since verifySignature is export property and not the export itself, signatureMiddleware module may be left as is, but modules that use it should be de-cached and re-imported in tests where they are expected to use verifySignature. If the behaviour should be same for entire test suite, this should be performed in beforeEach as well. E.g. if these middlewares are used in app module directly, it's:
const decache = require('decache');
...
describe(() => {
let app;
beforeEach(() => {
verifySignatureStub = sinon.stub(signatureMiddleware, 'verifySignature');
...
});
beforeEach(() => {
decache('./app');
app = require('./app');
});
Related
I am writing a test that looks like this:
import { getAllUsers } from "./users";
import { getMockReq, getMockRes } from "#jest-mock/express";
import User from "../../models/User";
jest.mock("../../models/User", () => ({
find: jest.fn(), // I want to change the return value of this mock in each test.
}));
describe("getAllUsers", () => {
test("makes request to database", async () => {
const req = getMockReq();
const { res, next, clearMockRes } = getMockRes();
await getAllUsers(req, res, next);
expect(User.find).toHaveBeenCalledTimes(1);
expect(User.find).toHaveBeenCalledWith();
});
});
Within the jest.mock statement, I am creating a mock of the imported 'User' dependency, specifically for the User.find() method. What I would like to do is set the return value of the User.find() method within each test that I write. Is this possible?
This SO question is similar, but my problem is that I can't import the 'find' method individually, it only comes packaged within the User dependency.
Well after much trial and error, here is a working solution:
import { getAllUsers } from "./users";
import { getMockReq, getMockRes } from "#jest-mock/express";
import User from "../../models/User";
const UserFindMock = jest.spyOn(User, "find");
const UserFind = jest.fn();
UserFindMock.mockImplementation(UserFind);
describe("getAllUsers", () => {
test("makes request to database", async () => {
UserFind.mockReturnValue(["buster"]);
const req = getMockReq();
const { res, next, clearMockRes } = getMockRes();
await getAllUsers(req, res, next);
expect(User.find).toHaveBeenCalledTimes(1);
expect(User.find).toHaveBeenCalledWith();
expect(res.send).toHaveBeenCalledWith(["buster"]);
});
});
Note how I've used jest.spyOn to set a jest.fn() on the specific User find method, and I can use the UserFind variable to set the return value of the mock implementation.
I have an app which tallies the number of visits to the url. The tallying is done in Redis. I'm using redis-mock which simulates commands like INCR in memory.
The following test visits the page 3 times and expects the response object to report current as 3:
let app = require('./app');
const supertest = require("supertest");
jest.mock('redis', () => jest.requireActual('redis-mock'));
/* Preceeded by the exact same test */
it('should report incremented value on multiple requests', (done) => {
const COUNT = 3;
const testRequest = function (cb) { supertest(app).get('/test').expect(200, cb) };
async.series([
testRequest,
testRequest,
testRequest
], (err, results) => {
if (err) console.error(err);
const lastResponse = _.last(results).body;
expect(
lastResponse.current
).toBe(COUNT);
done();
});
});
The issue is that if I keep reusing app, the internal "redis" mock will continue getting incremented between tests.
I can side-step this a bit by doing this:
beforeEach(() => {
app = require('./app');
jest.resetAllMocks();
jest.resetModules();
});
Overwriting app seems to do the trick but isn't there a way to clean-up the "internal" mocked module somehow between tests?
My guess is that somehow the '/test' endpoint gets invoked in some other tests in the suite, you could try to run specific parts of your suite using .only or even trying to run the entire suite serially.
To answer the original questions the entire suite must be isolated and consistent either if you are running a specific test case scenario or if you are trying to run the entire suite, thus you need to clear up any leftovers that they could actually affect the results.
So you can actually use the .beforeEach or the .beforeAll methods, provided by Jest in order to "mock" Redis and the .afterAll method for clearance.
A dummy implementation would look like this:
import redis from "redis";
import redis_mock from "redis-mock";
import request from "supertest";
jest.mock("redis", () => jest.requireActual("redis-mock"));
// Client to be used for manually resetting the mocked redis database
const redisClient = redis.createClient();
// Sometimes order matters, since we want to setup the mock
// and boot the app afterwards
import app from "./app";
const COUNT = 3;
const testRequest = () => supertest(app).get("/test");
describe("testing", () => {
afterAll((done) => {
// Reset the mock after the tests are done
jest.clearAllMocks();
// You can also flush the mocked database here if neeeded and close the client
redisClient.flushall(done);
// Alternatively, you can also delete the key as
redisClient.del("test", done);
redisClient.quit(done);
});
it("dummy test to run", () => {
expect(true).toBe(true);
});
it("the actual test", async () => {
let last;
// Run the requests in serial
for (let i = 0; i < COUNT - 1; i++) {
last = await testRequest();
}
// assert the last one
expect(last.status).toBe(200);
expect(last.body.current).toBe(COUNT);
});
});
I'm making a simple API with Express and I'm trying to add tests with Jest but when I try to run the tests it displays the next error:
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
at BufferList.Readable (node_modules/readable-stream/lib/_stream_readable.js:179:22)
at BufferList.Duplex (node_modules/readable-stream/lib/_stream_duplex.js:67:12)
at new BufferList (node_modules/bl/bl.js:33:16)
at new MessageStream (node_modules/mongodb/lib/cmap/message_stream.js:35:21)
at new Connection (node_modules/mongodb/lib/cmap/connection.js:52:28)
/home/jonathangomz/Documents/Node/Express/Devotionals/node_modules/readable-stream/lib/_stream_readable.js:111
var isDuplex = stream instanceof Duplex;
^
TypeError: Right-hand side of 'instanceof' is not callable
I'm not sure to trust the result if right after jest break (or something like that):
My test is:
const app = require("../app");
const request = require("supertest");
describe("Testing root router", () => {
test("Should test that true === true", async () => {
jest.useFakeTimers();
const response = await request(app).get("/");
expect(response.status).toBe(200);
});
});
My jest configuration on package.json:
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
]
}
Notes:
I read about jest.useFakeTimers() but It's not working and I'm not sure if I'm using in the wrong way. I also tried adding it to the beforeEach method but nothing.
In my case, I had to add the package to transformIgnorePatterns in the jest config.
Add jest.useFakeTimers('modern') before the asynchronous call. Add jest.runAllTimers() after the asynchronous call. This will fast-forward timers for you.
const app = require("../app")
const request = require("supertest")
describe("Testing root router", () => {
test("Should test that true === true", async () => {
//Before asynchronous call
jest.useFakeTimers("modern")
const response = await request(app).get("/")
//After asynchronous call
jest.runAllTimers()
expect(response.status).toBe(200)
})
})
Try adding --testTimeout=10000 flag when calling jest, it works for me.
Information based on Testing NodeJs/Express API with Jest and Supertest
--testTimeout flag - This increases the default timeout of Jest which is 5000ms. This is important since the test runner needs to refresh the database before running the test
By adding jest.useFakeTimers() just after all your import.
What about making your test async ?
const app = require("../app");
const request = require("supertest");
describe("Testing root router",async () => {
test("Should test that true === true", async () => {
jest.useFakeTimers();
const response = await request(app).get("/");
expect(response.status).toBe(200);
});
});
I have an Express app that uses node-slack-sdk to make posts to Slack when certain endpoints are hit. I am trying to write integration tests for a route that, among many other things, calls a method from that library.
I would like to prevent all default behavior of certain methods from the Slack library, and simply assert that the methods were called with certain arguments.
I have attempted to simplify the problem. How can I stub a method (which is actually nested within chat) of an instance of an WebClient, prevent the original functionality, and make assertions about what arguments it was called with?
I've tried a lot of things that haven't worked, so I'm editing this and providing a vastly simplified set-up here:
index.html:
const express = require('express');
const {WebClient} = require('#slack/client');
const app = express();
const web = new WebClient('token');
app.post('/', (req, res) => {
web.chat.postMessage({
text: 'Hello world!',
token: '123'
})
.then(() => {
res.json({});
})
.catch(err => {
res.sendStatus(500);
});
});
module.exports = app;
index.test.html
'use strict';
const app = require('../index');
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const {WebClient} = require('#slack/client');
describe('POST /', function() {
before(function() {
// replace WebClient with a simplified implementation, or replace the whole module.
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
// assert that web.chat.postMessage was called with {message: 'Hello world!'}, etc
});
});
});
There are a few things that make this difficult and unlike other examples. One, we don't have access to the web instance in the tests, so we can't stub the methods directly. Two, the method is buried within the chat property, web.chat.postMessage, which is also unlike other examples I've seen in sinon, proxyquire, etc documentation.
The design of your example is not very testable which is why you're having these issues. In order to make it more testable and cohesive, it's better to pass in your WebClient object and other dependencies, rather than create them in your route.
const express = require('express');
const {WebClient} = require('#slack/client');
const app = express();//you should be passing this in as well. But for the sake of this example i'll leave it
module.exports = function(webClient) {
app.post('/', (req, res) => {
web.chat.postMessage({
text: 'Hello world!',
token: '123'
})
.then(() => {
res.json({});
})
.catch(err => {
res.sendStatus(500);
});
})
return app;
};
In order to implement this, build your objects/routes at a higher module. (You might have to edit what express generated for you. I'm not sure, personally I work with a heavily refactored version of express to fit my needs.) By passing in your WebClient you can now create a stub for your test.
'use strict';
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const {WebClient} = require('#slack/client');
const web = new WebClient('token');
let app = require('../index')(web);
describe('POST /', function() {
it('should call chat.update with specific arguments', function() {
const spy = sinon.spy();
sinon.stub(web.chat, 'postMessage').callsFake(spy);
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
assert(spy.calledWith({message: 'Hello world!'}));
});
});
});
This is known as Dependency Injection. Instead of having your index module build it's dependency, WebClient, your higher modules will pass in the dependency in order for the them to control the state of it's lower modules. Your higher module, your test, now has the control it needs to create a stub for the lower module, index.
The code above was just quick work. I haven't tested to see if it works, but it should answer your question.
So #Plee, has some good points in term of structuring. But my answer is more about the issue at hand, how to make the test work and things you need to understand. For getting better at writing unit tests you should use other good resources like books and articles, I assume there would be plenty of great resources online for the same
The first thing you do wrong in your tests is the first line itself
const app = require('../index');
Doing this, you load the index file which then executes the below code
const {WebClient} = require('#slack/client');
const app = express();
const web = new WebClient('token');
So now the module has loaded the original #slack/client and created an object which is not accessible outside the module. So we have lost our chance of customizing/spying/stubbing the module.
So the first thumb rule
Never load such modules globally in the test. Or otherwise never load them before stubbing
So next we want is that in our test, we should load the origin client library which we want to stub
'use strict';
const {WebClient} = require('#slack/client');
const sinon = require('sinon');
Now since we have no way of getting the created object in index.js, we need to capture the object when it gets created. This can be done like below
var current_client = null;
class MyWebClient extends WebClient {
constructor(token, options) {
super(token, options);
current_client = this;
}
}
require('#slack/client').WebClient = MyWebClient;
So now what we do is that original WebClient is replaced by our MyWebClient and when anyone creates an object of the same, we just capture that in current_client. This assumes that only one object will be created from the modules we load.
Next is to update our before method to stub the web.chat.postMessage method. So we update our before method like below
before(function() {
current_client = null;
app = require('../index');
var stub = sinon.stub();
stub.resolves({});
current_client.chat.postMessage = stub;
});
And now comes the testing function, which we update like below
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
expect(current_client.chat.postMessage
.getCall(0).args[0]).to.deep.equal({
text: 'Hello world!',
token: '123'
});
});
});
and the results are positive
Below is the complete index.test.js I used, your index.js was unchanged
'use strict';
const {WebClient} = require('#slack/client');
const sinon = require('sinon');
var current_client = null;
class MyWebClient extends WebClient {
constructor(token, options) {
super(token, options);
current_client = this;
}
}
require('#slack/client').WebClient = MyWebClient;
const chai = require('chai');
const chaiHttp = require('chai-http');
const expect = chai.expect;
chai.use(chaiHttp);
let app = null;
describe('POST /', function() {
before(function() {
current_client = null;
app = require('../index');
var stub = sinon.stub();
stub.resolves({});
current_client.chat.postMessage = stub;
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
expect(current_client.chat.postMessage
.getCall(0).args[0]).to.deep.equal({
text: 'Hello world!',
token: '123'
});
});
});
});
Based on the other comments, it seems like you are in a codebase where making a drastic refactor would be difficult. So here is how I would test without making any changes to your index.js.
I'm using the rewire library here to get and stub out the web variable from the index file.
'use strict';
const rewire = require('rewire');
const app = rewire('../index');
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const web = app.__get__('web');
describe('POST /', function() {
beforeEach(function() {
this.sandbox = sinon.sandbox.create();
this.sandbox.stub(web.chat);
});
afterEach(function() {
this.sandbox.restore();
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
const called = web.chat.postMessage.calledWith({message: 'Hello world!'});
expect(called).to.be.true;
});
});
});
Background
I have a small server that receives data from a machine. Every time I receive a message I call a function in a dispatcher object that simply console.logs everything it receives.
Problem
The code works well as I can see the console.logs in the console, but Sinon spy.called doesn't work. It is always false no matter how many times I call dispatcher.onMessage.
Code
server.js
const eventDispatcher = {
onMessage: console.log,
};
const server = (dispatcher = eventDispatcher) => {
//this gets called everytime the server receives a message
const onData = data => {
//Process data
//....
dispatcher.onMessage(data);
};
const getDispatcher = () => dispatcher;
return Object.freeze({
getDispatcher
});
};
test.js
describe("message sender", () => {
const myServer = serverFactory();
it("should send information to server", () => {
dummyMachine.socket.write("Hello World!\r\n");
const dataSpy = sinon.spy(myServer.getDispatcher(), "onMessage");
expect(dataSpy.called).to.be.true; //always fails!
});
});
Research
After reading similar posts I believe this happens due to some layer of indirection, as pointed in:
Sinon Spy is not called if the spied method is called indirectly
And should be fixed via using this:
Sinon.spy on a method is not invoked
However, looking at my code I really can't get what I am missing.
Question
What am I doing wrong?
MCVE
Directory Structure
Project_Folder
|____package.json
|____server.js
|____test
|____ dummyMachine_spec.js
package.json
{
"name": "sinon-question",
"version": "1.0.0",
"description": "MCVE about a dummy machine connecting to a server for StackOverflow",
"main": "server.js",
"scripts": {
"test": "NODE_ENV=test mocha --reporter spec --slow 5000 --timeout 5000 test/*_spec.js || true"
},
"author": "Pedro Miguel P. S. Martins",
"license": "ISC",
"devDependencies": {
"chai": "^3.5.0",
"mocha": "^3.3.0",
"sinon": "^2.2.0"
},
"dependencies": {
"net": "^1.0.2"
}
}
server.js
"use strict";
const net = require("net");
const eventDispatcher = {
onMessage: console.log,
};
const server = (dispatcher = eventDispatcher) => {
let serverSocket;
const onData = data => {
//Process data
dispatcher.onMessage(`I am server and I got ${data}`);
};
const start = (connectOpts) => {
return new Promise(fulfil => {
serverSocket = net.createConnection(connectOpts, () => {
serverSocket.on("data", onData);
fulfil();
});
});
};
const stop = () => serverSocket.destroy();
const getDispatcher = () => dispatcher;
return Object.freeze({
start,
stop,
getDispatcher
});
};
module.exports = server;
test/dummyMachine.js
"use strict";
const chai = require("chai"),
expect = chai.expect;
const sinon = require("sinon");
const net = require("net");
const serverFactory = require("../server.js");
describe("Dummy Machine", () => {
const dummyMachine = {
IP: "localhost",
port: 4002,
server: undefined,
socket: undefined
};
const server = serverFactory();
before("Sets up dummyReader and server", done => {
dummyMachine.server = net.createServer(undefined, socket => {
dummyMachine.socket = socket;
});
dummyMachine.server.listen(
dummyMachine.port,
dummyMachine.IP,
undefined,
() => {
server.start({
host: "localhost",
port: 4002
})
.then(done);
}
);
});
after("Kills dummyReader and server", () => {
server.stop();
dummyMachine.server.close();
});
it("should connect to server", done => {
dummyMachine.server.getConnections((err, count) => {
expect(err).to.be.null;
expect(count).to.eql(1);
done();
});
});
it("should send information to server", () => {
dummyMachine.socket.write("Hello World\r\n");
const dataSpy = sinon.spy(server.getDispatcher(), "onMessage");
expect(dataSpy.called).to.be.true; //WORK DAAMN YOU!
});
});
Instructions for MCVE
Download the files and create the directory structure indicated.
Enter project folder and type npm install on a terminal
Type npm test
The first test should pass, meaning a connection is in fact being made.
The second test will fail, even though you get the console log, proving that onMessage was called.
The main problem is that it's not enough to just spy on onMessage, because your test will never find out when it got called exactly (because stream events are asynchronously delivered).
You can use a hack using setTimeout(), and check and see if it got called some time after sending a message to the server, but that's not ideal.
Instead, you can replace onMessage with a function that will get called instead, and from that function, you can test and see if it got called with the correct arguments, etc.
Sinon provides stubs which can be used for this:
it("should send information to server", done => {
const stub = sinon.stub(server.getDispatcher(), 'onMessage').callsFake(data => {
stub.restore();
expect(data).to.equal('I am server and I got Hello World\r\n');
done();
});
dummyMachine.socket.write("Hello World\r\n");
});
Instead of the original onMessage, it will call the "fake function" that you provide. There, the stub is restored (which means that onMessage is restored to its original), and you can check and see if it got called with the correct argument.
Because the test is asynchronous, it's using done.
There are a few things to consider:
You can't easily detect if, because of a programming error, onMessage doesn't get called at all. When that happens, after a few seconds, Mocha will timeout your test, causing it to fail.
If that happens, the stub won't be restored to its original and any subsequent tests that try to stub onMessage will fail because the method is already stubbed (usually, you work around this by creating the stub in an onBeforeEach and restore it in an onAfterEach)
This solution won't test the inner workings of onMessage, because it's being replaced. It only tests if it gets called, and with the correct argument (however, it's better, and easier, to test onMessage separately, by directly calling it with various arguments from your test cases).
I would guess that the problem is caused by using Object.freeze on the object that you would like to spy on.
Most of the time these "spy on" techniques work by overwriting the spied upon function with another function that implements the "spying" functionality (eg: keeps track of the function calls).
But if you're freezing the object, then the function cannot be overwritten.