Sinon spy.called no working - javascript

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.

Related

How to clean-up / reset redis-mock in an Express Jest test?

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);
});
});

Mocking node_modules which return a function with Jest?

I am writing a typeScript program which hits an external API. In the process of writing tests for this program, I have been unable to correctly mock-out the dependency on the external API in a way that allows me to inspect the values passed to the API itself.
A simplified version of my code that hits the API is as follows:
const api = require("api-name")();
export class DataManager {
setup_api = async () => {
const email = "email#website.ext";
const password = "password";
try {
return api.login(email, password);
} catch (err) {
throw new Error("Failure to log in: " + err);
}
};
My test logic is as follows:
jest.mock("api-name", () => () => {
return {
login: jest.fn().mockImplementation(() => {
return "200 - OK. Log in successful.";
}),
};
});
import { DataManager } from "../../core/dataManager";
const api = require("api-name")();
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("200 - OK. Log in successful.");
expect(api.login).toHaveBeenCalledTimes(1);
});
});
What I find perplexing is that the test assertion which fails is only expect(api.login).toHaveBeenCalledTimes(1). Which means the API is being mocked, but I don't have access to the original mock. I think this is because the opening line of my test logic is replacing login with a NEW jest.fn() when called. Whether or not that's true, I don't know how to prevent it or to get access to the mock function-which I want to do because I am more concerned with the function being called with the correct values than it returning something specific.
I think my difficulty in mocking this library has to do with the way it's imported: const api = require("api-name")(); where I have to include an opening and closing parenthesis after the require statement. But I don't entirely know what that means, or what the implications of it are re:testing.
I came across an answer in this issue thread for ts-jest. Apparently, ts-jest does NOT "hoist" variables which follow the naming pattern mock*, as regular jest does. As a result, when you try to instantiate a named mock variable before using the factory parameter for jest.mock(), you get an error that you cannot access the mock variable before initialization.
Per the previously mentioned thread, the jest.doMock() method works in the same way as jest.mock(), save for the fact that it is not "hoisted" to the top of the file. Thus, you can create variables prior to mocking out the library.
Thus, a working solution is as follows:
const mockLogin = jest.fn().mockImplementation(() => {
return "Mock Login Method Called";
});
jest.doMock("api-name", () => () => {
return {
login: mockLogin,
};
});
import { DataManager } from "../../core/dataManager";
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("Mock Login Method Called");
expect(mockLogin).toHaveBeenCalledWith("email#website.ext", "password");
});
});
Again, this is really only relevant when using ts-jest, as using babel to transform your jest typescript tests WILL support the correct hoisting behavior. This is subject to change in the future, with updates to ts-jest, but the jest.doMock() workaround seems good enough for the time being.

Import a file after the Jest environment has been torn down

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);
});
});

Stubbing express middleware functions with sinon is

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');
});

Mocha Async before() hook timing out

Background
I am creating a dummy server using net from Node.js in my Mocha test.
I have a dummy test, and I want to start the server before the test starts, and kill it after:
"use strict";
/*global describe, it, expect, before, after*/
const net = require("net");
describe("dummy server test", () => {
const dummyReader = {
IP: "localhost",
port: 4002,
server: undefined,
socket: undefined
};
before("Starts dummy server", done => {
dummyReader.server = net.createServer(socket => {
dummyReader.socket = socket;
done();
});
dummyReader.server.listen(dummyReader.IP, dummyReader.port);
});
after("Kills dummy server", done => {
dummyReader.server.close();
dummyReader.socket.destroy();
done();
});
it("should pass", () => {
expect(true).to.be.true;
});
});
Problem
The problem is that my async before hook never completes. For a reason I can't understand done is never called, and thus the hook times out.
I tried increasing the time out, believing it could fix the issue, but to no avail.
Question
How can I fix my code?
There are two problems with your code:
You need to flip the host address and port arguments in dummyReader.server.listen(...);. The port comes first, and the host second.
The callback to net.createServer won't be called until something actually connects to the server, but you have nothing connecting to it.
With the following before hook the code will run. I've added code that creates a connection right away for illustration purposes.
before("Starts dummy server", done => {
dummyReader.server = net.createServer(socket => {
dummyReader.socket = socket;
done();
});
dummyReader.server.listen(dummyReader.port,
dummyReader.IP,
undefined,
() => {
// For illustration purposes,
// create a connection as soon
// as the server is listening.
net.connect(
dummyReader.port,
dummyReader.IP);
});
});
Seems to me though that what you should be doing is ending the before hook as soon as the server is listening and then connect to it in your tests. Here's an illustration of how it can be done:
"use strict";
/*global describe, it, expect, before, after*/
const net = require("net");
describe("dummy server test", () => {
const dummyReader = {
IP: "localhost",
port: 6002,
server: undefined,
socket: undefined
};
before("Starts dummy server", done => {
dummyReader.server = net.createServer(socket => {
console.log("got a new socket!");
dummyReader.socket = socket;
});
dummyReader.server.listen(dummyReader.port,
dummyReader.IP,
undefined,
() => {
done();
});
});
after("Kills dummy server", done => {
dummyReader.server.close();
// dummyReader.socket.destroy();
done();
});
let prevSocket;
it("should pass", (done) => {
net.connect(dummyReader.port, dummyReader.IP, () => {
console.log(dummyReader.socket.address());
prevSocket = dummyReader.socket;
done();
});
});
it("should pass 2", (done) => {
net.connect(dummyReader.port, dummyReader.IP, () => {
console.log(dummyReader.socket.address());
console.log("same socket?",
prevSocket === dummyReader.socket);
done();
});
});
});
Each time you connect, a new net.Socket object is created and assigned to dummyReader.socket and so you can access it from inside you test if needed. I've peppered the code with console.log statements to show some key values. When I run it here, I get:
dummy server test
got a new socket!
{ address: '127.0.0.1', family: 'IPv4', port: 6002 }
✓ should pass
got a new socket!
{ address: '127.0.0.1', family: 'IPv4', port: 6002 }
same socket? false
✓ should pass 2
2 passing (71ms)

Categories

Resources