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
Post a Comment