iraklisg
iraklisg

Reputation: 5799

How to mock a function using rewirejs and chai-spies in order to test it?

tl;dr

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.

My set-up

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

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

Answers (1)

iraklisg
iraklisg

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.

A better solution

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

Related Questions