How to stub a private method of a class written in typescript using sinon

Viewed 15118

I am writing unit tests for a public method which is, in turn, calling a private method of the class written in typescript (Node JS).

Sample Code

class A {
   constructor() {  
   }
   public method1() {  
       if(this.method2()) {
          // Do something
       } else {
          // Do something else
       }
   }
   private method2() {
      return true;
   }
}

Now to test method1() I need to stub method2() which is a private method.

here what I am trying :

sinon.stub(A.prototype, "method2");

Typescript is throwing the error :

Argument of type '"method2"' is not assignable to parameter of type '"method1"'

Any help would be appreciated. Thank You

2 Answers

The problem is that the definition for sinon uses the following definition for the stub function :

interface SinonStubStatic { <T>(obj: T, method: keyof T): SinonStub; }

This means that the second parameter must be the name of a member (a public one) of the T type. This is probably a good restriction generally, but in this case it is a bit too restrictive.

You can get around it by casting to any:

sinon.stub(A.prototype, <any>"method2");

Sometimes when the complexity of code and tests is more significant I prefer to "externalize" private methods. You can do that, that either with a (partial) class or a (partial) interface.

    it('private methods test', async () => {
        // original class
        class A{
            public method1():string{
                if(this.method2()) {
                    // Do something
                    return "true";
                 } else {
                    // Do something else
                    return "false";
                 }
            }
            // with private method
            private method2():boolean{
                return true;
            }
        }

        // interface that makes the private method public
        interface IAExternalized{
            method2():boolean;
        }

        // class that makes the private method public
        class APrivate implements IAExternalized{
            // with public method
            method2():boolean{
                return true;
            };
        }

        // test before mocking
        let test:A = new A();
        let result:string = test.method1();
        result.should.be.equal("true");

        // let's mock the private method, but with typechecking available
        let stubMethod2:sinon.SinonStub = sinon.stub(<IAExternalized><unknown>(A.prototype), "method2").returns(false);

        result = test.method1();
        result.should.not.be.equal("true");
        result.should.be.equal("false");

        // access private method of an object through public-interface
        let testPrivate:IAExternalized = <IAExternalized><unknown>test;
        let result2:boolean = testPrivate.method2();
        result2.should.not.be.equal(true);
        result2.should.be.equal(false);
    });

NOTE: If you control the code you are testing, you do not need to double code, prone to mistakes, but you can make your class implement the interface. To convert standard (without private) interface into "externalized" you can extend it with public methods.

export interface IAExternalized extends IAPrivate {
   method2():boolean
};
Related