How to write unit testing for Angular / TypeScript for private methods with Jasmine

tymspy picture tymspy · Mar 14, 2016 · Viewed 111.4k times · Source

How do you test a private function in angular 2 ?

class FooBar {

    private _status: number;

    constructor( private foo : Bar ) {
        this.initFooBar();

    }

    private initFooBar(){
        this.foo.bar( "data" );
        this._status = this.fooo.foo();
    }

    public get status(){
        return this._status;
    }

}

The solution I found

  1. Put the test code itself inside the closure or Add code inside the closure that stores references to the local variables on existing objects in the outer scope.

    Later strip out the test code using a tool. http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/

Please suggest me a better way to solve this problem if you have done any?

P.S

  1. Most of the answer for similar type of question like this one doesn't give a solution to problem, that's why I'm asking this question

  2. Most of the developer say you Don’t test private functions but I don't say they are wrong or right, but there are necessities for my case to test private.

Answer

Aaron Beall picture Aaron Beall · Mar 14, 2016

I'm with you, even though it's a good goal to "only unit test the public API" there are times when it doesn't seem that simple and you feel you are choosing between compromising either the API or the unit-tests. You know this already, since that's exactly what you're asking to do, so I won't get into it. :)

In TypeScript I've discovered a few ways you can access private members for the sake of unit-testing. Consider this class:

class MyThing {

    private _name:string;
    private _count:number;

    constructor() {
        this.init("Test", 123);
    }

    private init(name:string, count:number){
        this._name = name;
        this._count = count;
    }

    public get name(){ return this._name; }

    public get count(){ return this._count; }

}

Even though TS restricts access to class members using private, protected, public, the compiled JS has no private members, since this isn't a thing in JS. It's purely used for the TS compiler. Therefor:

  1. You can assert to any and escape the compiler from warning you about access restrictions:

    (thing as any)._name = "Unit Test";
    (thing as any)._count = 123;
    (thing as any).init("Unit Test", 123);
    

    The problem with this approach is that the compiler simply has no idea what you are doing right of the any, so you don't get desired type errors:

    (thing as any)._name = 123; // wrong, but no error
    (thing as any)._count = "Unit Test"; // wrong, but no error
    (thing as any).init(0, "123"); // wrong, but no error
    

    This will obviously make refactoring more difficult.

  2. You can use array access ([]) to get at the private members:

    thing["_name"] = "Unit Test";
    thing["_count"] = 123;
    thing["init"]("Unit Test", 123);
    

    While it looks funky, TSC will actually validate the types as if you accessed them directly:

    thing["_name"] = 123; // type error
    thing["_count"] = "Unit Test"; // type error
    thing["init"](0, "123"); // argument error
    

    To be honest I don't know why this works. This is apparently an intentional "escape hatch" to give you access to private members without losing type safety. This is exactly what I think you want for your unit-testing.

Here is a working example in the TypeScript Playground.

Edit for TypeScript 2.6

Another option that some like is to use // @ts-ignore (added in TS 2.6) which simply suppresses all errors on the following line:

// @ts-ignore
thing._name = "Unit Test";

The problem with this is, well, it suppresses all errors on the following line:

// @ts-ignore
thing._name(123).this.should.NOT.beAllowed("but it is") = window / {};

I personally consider @ts-ignore a code-smell, and as the docs say:

we recommend you use this comments very sparingly. [emphasis original]