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

Problem

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.

Problem courtesy of: tymspy

Solution

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
    
  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. It seems that array brackets don't enforce access restrictions, but type inference gives you full type safety. This is exactly what I think you want for your unit-testing.

Here is a working example in the TypeScript Playground.

Solution courtesy of: Aaron

Discussion

The point of "don't test private methods" really is Test the class like someone who uses it.

If you have a public API with 5 methods, any consumer of your class can use these, and therefore you should test them. A consumer should not access the private methods/properties of your class, meaning you can change private members when the public exposed functionality stays the same.


If you rely on internal extensible functionality, use protected instead of private.
Note that protected is still a public API (!), just used differently.

class OverlyComplicatedCalculator {
    public add(...numbers: number[]): number {
        return this.calculate((a, b) => a + b, numbers);
    }
    // can't be used or tested via ".calculate()", but it is still part of your public API!
    protected calculate(operation, operands) {
        let result = operands[0];
        for (let i = 1; i < operands.length; operands++) {
            result = operation(result, operands[i]);
        }
        return result;
    }
}

Unit test protected properties in the same way a consumer would use them, via subclassing:

it('should be extensible via calculate()', () => {
    class TestCalculator extends OverlyComplicatedCalculator {
        public testWithArrays(array: any[]): any[] {
            const concat = (a, b) => [].concat(a, b);
            // tests the protected method
            return this.calculate(concat, array);
        }
    }
    let testCalc = new TestCalculator();
    let result = testCalc.testWithArrays([1, 'two', 3]);
    expect(result).toEqual([1, 'two', 3]);
});
Discussion courtesy of: Leon Adler

As most of the developers don't recommend testing private function, Why not test it?.

Eg.

YourClass.ts

export class FooBar {
  private _status: number;

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

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

TestYourClass.spec.ts

describe("Testing foo bar for status being set", function() {

...

//Variable with type any
let fooBar;

fooBar = new FooBar();

...
//Method 1
//Now this will be visible
fooBar.initFooBar();

//Method 2
//This doesn't require variable with any type
fooBar['initFooBar'](); 
...
}

Thanks to @Aaron, @Thierry Templier.

Discussion courtesy of: tymspy

Do not write tests for private methods. This defeats the point of unit tests.

  • You should be testing the public API of your class
  • You should NOT be testing the implimentation details of your class

Example

class SomeClass {

  public addNumber(a: number, b: number) {
      return a + b;
  }
}

The test for this method should not need to change if later the implementation changes but the behaviour of the public API remains the same.

class SomeClass {

  public addNumber(a: number, b: number) {
      return this.add(a, b);
  }

  private add(a: number, b: number) {
       return a + b;
  }
}

Don't make methods and properties public just in order to test them. This usually means that either:

  1. You are trying to test implementation rather than API (public interface).
  2. You should move the logic in question into its own class to make testing easier.
Discussion courtesy of: Martin

I agree with @toskv: I WOULDN'T RECOMMEND TO DO THAT:-)

But if you really want to test your private method, you can be aware that the corresponding code for the TypeScript correspond to a method of the constructor function prototype. This means that it can be used at runtime (whereas you will probably have some compilation errors).

For example:

export class FooBar {
  private _status: number;

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

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

will be transpiled into:

(function(System) {(function(__moduleName){System.register([], function(exports_1, context_1) {
  "use strict";
  var __moduleName = context_1 && context_1.id;
  var FooBar;
  return {
    setters:[],
    execute: function() {
      FooBar = (function () {
        function FooBar(foo) {
          this.foo = foo;
          this.initFooBar({});
        }
        FooBar.prototype.initFooBar = function (data) {
          this.foo.bar(data);
          this._status = this.foo.foo();
        };
        return FooBar;
      }());
      exports_1("FooBar", FooBar);
    }
  }
})(System);

See this plunkr: https://plnkr.co/edit/calJCF?p=preview.

Discussion courtesy of: Thierry Templier

This recipe can be found in it's original form on Stack Over Flow.