Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

People also ask

Can we write unit test for private methods?

Unit Tests Should Only Test Public Methods The short answer is that you shouldn't test private methods directly, but only their effects on the public methods that call them. Unit tests are clients of the object under test, much like the other classes in the code that are dependent on the object.

How do you test private methods in unit testing?

To test private methods, you just need to test the public methods that call them. Call your public method and make assertions about the result or the state of the object. If the tests pass, you know your private methods are working correctly.


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]


You CAN call private methods.

If you encountered the following error:

expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/)
// TS2341: Property 'initFooBar' is private and only accessible within class 'FooBar'

just use // @ts-ignore:

// @ts-ignore
expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/)

Thank @Moff452 for his/her comment. You can also write:

expect(new FooBar(/*...*/)['initFooBar']()).toEqual(/*...*/)

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.


This worked for me:

Instead of:

sut.myPrivateMethod();

This:

sut['myPrivateMethod']();

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.

Sorry for the necro on this post, but I feel compelled to weigh in on a couple of things that do not seem to have been touched on.

First a foremost - when we find ourselves needing access to private members on a class during unit testing, it is generally a big, fat red flag that we've goofed in our strategic or tactical approach and have inadvertently violated the single responsibility principal by pushing behavior where it does not belong. Feeling the need to access methods that are really nothing more than an isolated subroutine of a construction procedure is one of the most common occurrences of this; however, it's kind of like your boss expecting you to show up for work ready-to-go and also having some perverse need to know what morning routine you went through to get you into that state...

The other most common instance of this happening is when you find yourself trying to test the proverbial "god class." It is a special kind of problem in and of itself, but suffers from the same basic issue with needing to know intimate details of a procedure - but that's getting off topic.

In this specific example, we've effectively assigned the responsibility of fully initializing the Bar object to the FooBar class's constructor. In object oriented programming, one of the core tenents is that the constructor is "sacred" and should be guarded against invalid data that would invalidate its' own internal state and leave it primed to fail somewhere else downstream (in what could be a very deep pipeline.)

We've failed to do that here by allowing the FooBar object to accept a Bar that is not ready at the time that the FooBar is constructed, and have compensated by sort-of "hacking" the FooBar object to take matters into its' own hands.

This is the result of a failure to adhere to another tenent of object oriented programming (in the case of Bar,) which is that an object's state should be fully initialized and ready to handle any incoming calls to its' public members immediately after creation. Now, this does not mean immediately after the constructor is called in all instances. When you have an object that has many complex construction scenarios, then it is better to expose setters to its optional members to an object that is implemented in accordance with a creation design-pattern (Factory, Builder, etc...) In any of the latter cases, you would be pushing the initialization of the target object off into another object graph whose sole purpose is directing traffic to get you to a point where you have a valid instance of that which you are requesting - and the product should not be considered "ready" until after this creation object has served it up.

In your example, the Bar's "status" property does not seem to be in a valid state in which a FooBar can accept it - so the FooBar does something to it to correct that issue.

The second issue I am seeing is that it appears that you are trying to test your code rather than practice test-driven development. This is definitely my own opinion at this point in time; but, this type of testing is really an anti-pattern. What you end up doing is falling into the trap of realizing that you have core design problems that prevent your code from being testable after the fact, rather than writing the tests you need and subsequently programming to the tests. Either way you come at the problem, you should still end up with the same number of tests and lines of code had you truly achieved a SOLID implementation. So - why try and reverse engineer your way into testable code when you can just address the matter at the onset of your development efforts?

Had you done that, then you would have realized much earlier on that you were going to have to write some rather icky code in order to test against your design and would have had the opportunity early on to realign your approach by shifting behavior to implementations that are easily testable.


call private method using square brackets

Ts file

class Calculate{
  private total;
  private add(a: number) {
      return a + total;
  }
}

spect.ts file

it('should return 5 if input 3 and 2', () => {
    component['total'] = 2;
    let result = component['add'](3);
    expect(result).toEqual(5);
});