Stubbing a Javascript Generator function



Recently, I had to test a generator function, which was calling another generator function from within it. I learnt something on how to stub a generator function.

(Note: Code is not tested for accuracy. The code is only used to illustrate the concept).

Use case:

Following is a simplified example which describes the scenario:

The use case here is to check the database if a user already exists in the database. If so, the user should be added. If not the user should be added.

Project Structure:

Objects
Functions
UserDao
1)    function* findUser(userId:String):User
2)    function* saveUser(user:User):User
UserHelper
1)    function* saveUser(user:User):User
UserController
1)    function saveUser(user:User):User

When a request is made to add the user, the request lands in the saveUser function in the UserController. The controller uses a co-generator to call the saveUser generator function in the helper.

If the user is created (i.e if duplicate does not exist), it logs ‘Success’. Otherwise it logs 'user already exists'.

Code:

const co = require('co');

//Test user
const user={
  name:'name',
  id: 'id',
};

//The makeshift Database
var users = [];

//The Dao layer
const userDao = {
  saveUser: function* (user){ //Saves a given user to users array
     users = users.concat(user);
     yield users;
  },
  fetchUser: function* (user){ //Fetches user from the users array
    console.log('users found = ',users);
    const found = users.find(u=>u.name === user.name);
    if(found){
      return yield [found];
    } else {
      return yield [];
    }
  }
};

//The helper layer
const userHelper = {
  saveUser: function* (user){ //Checks if the user is present. If not present, it saves
    const userFetched = yield* userDao.fetchUser(user);
    if(!userFetched || !userFetched.length) {
      yield* userDao.saveUser(user);
      return 'Success';
    } else{
      return 'user already exists';
    }
  }
};

//Controller
const saveUser = (user) =>{
  co(userHelper.saveUser.bind(null,user))
  .then(u=>{
    console.log('inside then = ' ,u);
  })
  .catch(err=>{
    console.log('inside else = ',err);
  });
};

As you can see the Controller calls the helper through a co-generator. The helper calls the dao method to check if the user exists. If the user does not the user is created.

Note: For simplicity, I have used a local array as the database. In a more real scenario, this will be an asynchronous call to the Database synchronized by the yield keyword.

Unit Testing:

The issue I faced was when I tried to unit test the saveUser method in the helper. Since it was a unit test, I didn’t want the fetchUser or saveUser in the Dao to be invoked. Instead, I wanted to stub these methods. Stubbing saveUser is easy, because we are not expecting a response from saveUser.

The requirements we have for saveUser are:

  •       It should be called synchronously
  •      The call should not throw an exception.

So, creating a simple stub would suffice for this method, because all we want to test here is the callCount.

const saveUserDaoStub = sinon.stub(userDao,'saveUser');

However, the tricky part to test is the fetchUser method in Dao. This is a generator function and we are using yield* to call this function. The yield* expects a generatorFunctionHeader. Apart from that, we also need to be able to manipulate the output of fetchUser Dao method to return the result as if the user is found and also as if the user is not found. So using the normal way to stub as in:
const fetchUserDaoStub = sinon.stub(userDao,'fetchUser').returns([{name:’name’,id:’id’}]);

is not going to work. This is because yield* is expecting a generatorFunctionHeader. So how do we create a generatorFunctionHeader? Its quite simple. We can just stub the method and make it return a generatorFunctionHeader as follows:

const fetchUserDaoStub = sinon.stub(userDao,'fetchUser').returns(function* () {
    return [user];
      }());

We are returning the result we want from inside a generator function. This function call is passed as parameter to the returns method of the stub.

An example of the final test case:

describe('something',()=>{
  it('some other thing',()=>{
    const fetchUserDaoStub = sinon.stub(userDao,'fetchUser').returns(function* () {
    return [user];
      }());
    const saveUserDaoStub = sinon.stub(userDao,'saveUser');
    co(userHelper.saveUser.bind(null,user)).then(()=>{
      sinon.assert.callCount(fetchUserDaoStub,1);
      sinon.assert.callCount(saveUserDaoStub,0);
    }).catch((err)=>{
      console.log(err);
    })
  })
})




Comments

Popular posts from this blog

Docker Commands

Dockerfile

ES-6 classes