Reputation: 5799
I am trying to test an express app using mocha, chai, chai-spies and rewire.
In particular, what I am trying to do is to mock a function that exists in a module and use a chai spy instead.
I have a module called db.js
which exports a saveUser()
method
db.js
module.exports.saveUser = (user) => {
// saves user to database
};
The db module is required by app.js
module
app.js
const db = require('./db');
module.exports.handleSignUp = (email, password) => {
// create user object
let user = {
email: email,
password: password
};
// save user to database
db.saveUser(user); // <-- I want want to mock this in my test !!
};
Finally in my test file app.test.js
I have the following
app.test.js
const chai = require('chai')
, spies = require('chai-spies')
, rewire = require('rewire');
chai.use(spies);
const expect = chai.expect;
// Mock the db.saveUser method within app.js
let app = rewire('./app');
let dbMock = {
saveUser: chai.spy()
};
app.__set__('db', dbMock);
// Perform the test
it('should call saveUser', () => {
let email = '[email protected]'
, password = '123456';
// run the method we want to test
app.handleSignUp(email, password);
// assert that the spy is called
expect(dbMock.saveUser).to.be.spy; // <--- this test passes
expect(dbMock.saveUser).to.have.been.called(); // <--- this test fails
});
My problem is that my test for ensuring that the spy is called by app.handleSignUp fails as follows
AssertionError: expected { Spy } to have been called at Context.it (spies/app.test.js:25:40)
I sense that I am doing something wrong but I am stuck at the moment. Any help is appreciated, thank you
Upvotes: 2
Views: 3239
Reputation: 5799
Finally, I figured out what the problem was. From rewire github page:
Limitations
Using const It's not possible to rewire const (see #79). This can probably be solved with proxies someday but requires further research.
So, changing const db = require('./db');
to let db = require('./db');
in app.js
made all test pass.
However, since changing all const
declarations to let
in order to test an application with spies is a cumbersome, the following approach seems to be better:
We can require our db
module in app.js
as a const
as we did, but instead of creating the spy and overwriting the const variable:
let dbMock = {
saveUser: chai.spy()
};
app.__set__('db', dbMock);
we may use rewire
's getter method to import the db
module in our app.test.js
file, and then mock the saveUser()
method using our spy (that is to mutate one of the const
variable's properties; since objects in JS are passed by reference, getting and mutating the db
object within app.test.js module is also mutates the same object within app.js module)
const db = app.__get__('db');
db.saveUser = chai.spy()
Finally, we can expect that the mutated db.saveUser
(i.e our spy) will be called
expect(db.saveUser).to.have.been.called();
To sum up, both the db.js
and app.js
will not be changed, but the test file should now looks like the following:
const chai = require('chai')
, spies = require('chai-spies')
, rewire = require('rewire');
chai.use(spies);
let expect = chai.expect;
// Fetch the app object
let app = rewire('./app');
// Use the getter to read the const db object and mutate its saveUser property
const db = app.__get__('db');
db.saveUser = chai.spy()
// Finally perform the test using the mocked
it('should call saveUser', () => {
let email = '[email protected]'
, password = '123456';
// run the method we want to test
app.handleSignUp(email, password);
expect(db.saveUser).to.have.been.called(); // <--- now passes!!!
});
Upvotes: 4