Search code examples
javascriptnode.jsunit-testingsinon

How Sinon stubs function of an class that used internally


I wanna understand if Sinon can be used to stub a function from another class that is called internally.

For example there is a dbutil class:

class DBUtils {
    getMyResult(var1, var2){
        return new Promise((resolve) => {
            console.log("calling real getMyResult");//I still see this in the log
            resolve(var1 + var2);
        });
    }
}
module.exports = DBUtils;

It is called in another class dummyService:

const dbUtils = new DBUtils();
class dummyService {
    getResult(var1, var2){
    //initially in my code, it returns a promise, 
    //but I have replaced it with your code
    return dbUtils.getMyResult(var1, var2).then(item =>{
        console.log("finish getting my result!");
        console.log("here is my result: " + item);
        return item; 
    });
}
module.exports = dummyService;

Here is my unit test file: (Applied code change suggested by @Mark_M)

describe("dummy Test", function () {
var service = new DummyService();

const stub = sinon.stub(DBUtils.prototype, 'getMyResult');
stub.resolves("1234");    

it("get result", function() {
    service.getResult("ab", "cd").then(item => {
        console.log(item);
    });
});
});

Can anyone help me understand why my Stub is not working? and How could I stub the getMyResult function inside dbUtil class?

================ SOLUTION ===================

There are 2 things I missed:

  1. I should stub the DBUtils.prototype
sinon.stub(DBUtils.prototype, 'getMyResult').resolves("1234");
  1. there is a typo when I import the DBUtils in my test.

inside dummyService, I have

const DBUtils = require(./dbutils);

however, in the test file, I have

const DBUtils = require(./DBUtils);

Somehow, sinon didn't complained about it, and that's why my stub get never injected in the original method....after I correct the import in my test, I can see the stub result get injected now.


Solution

  • Since getMyResult() is a class method on your DBUtils class, you need to stub the prototype of DBUtils. Something like this:

    DBUtils.getMyResult() returns a promise. So you don't need to re-wrap it in a promise in dummyService.getResult(). It makes it a lot easier to reason about (and test) if you just return the original promise:

    dummyService:

    const DBUtils = require('./DBUtils')
    
    class dummyService {
        getResult(var1, var2){
            const dbUtils = new DBUtils() // <- I assume you are making an instance somewhere
            return dbUtils.getMyResult(var1, var2).then(item =>{
                console.log("finish getting my result!");
                console.log("here is my result: " + item);
                return item // <- if you want to access item in then you need to return it
            });
        }
    }
    module.exports = dummyService;
    

    Now in test.js:

    const DBUtils = require('./DBUtils')
    const DummyService = require('./dummy')
    const sinon = require('sinon')
    
    // stub the prototype
    const stub = sinon.stub(DBUtils.prototype, 'getMyResult')
    stub.resolves("Stubbed: 1234")
    
    // make an instance of the service
    const service = new DummyService()
    service.getResult("ab", "cd").then(item => {
        console.log(item)
        // this prints 'stubbed: 1234'
        // item is "abcd", which is the returned value of the real getMyResult method.
        // the expectation is, item should be "1234" as specified in the stub.
    });