How to write unit testing for Angular 2 / TypeScri

2019-01-16 05:43发布

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.

9条回答
虎瘦雄心在
2楼-- · 2019-01-16 06:09

This route I take is one where I create functions outside the class and assign the function to my private method.

export class MyClass {
  private _myPrivateFunction = someFunctionThatCanBeTested;
}

function someFunctionThatCanBeTested() {
  //This Is Testable
}

Now I don't know what type of OOP rules I am breaking, but to answer the question, this is how I test private methods. I welcome anyone to advise on Pros & Cons of this.

查看更多
The star\"
3楼-- · 2019-01-16 06:10

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.
查看更多
何必那么认真
4楼-- · 2019-01-16 06:13

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.

查看更多
爱情/是我丢掉的垃圾
5楼-- · 2019-01-16 06:14

As many have already stated, as much as you want to test the private methods you shouldn't hack your code or transpiler to make it work for you. Modern day TypeScript will deny most all of the hacks that people have provided so far.


Solution

TLDR; if a method should be tested then you should be decoupling the code into a class that you can expose the method to be public to be tested.

The reason you have the method private is because the functionality doesn't necessarily belong to be exposed by that class, and therefore if the functionality doesn't belong there it should be decoupled into it's own class.

Example

I ran across this article that does a great job of explaining how you should tackle testing private methods. It even covers some of the methods here and how why they're bad implementations.

https://patrickdesjardins.com/blog/how-to-unit-test-private-method-in-typescript-part-2

Note: This code is lifted from the blog linked above (I'm duplicating in case the content behind the link changes)

Before
class User{
    public getUserInformationToDisplay(){
        //...
        this.getUserAddress();
        //...
    }

    private getUserAddress(){
        //...
        this.formatStreet();
        //...
    }
    private formatStreet(){
        //...
    }
}
After
class User{
    private address:Address;
    public getUserInformationToDisplay(){
        //...
        address.getUserAddress();
        //...
    }
}
class Address{
    private format: StreetFormatter;
    public format(){
        //...
        format.ToString();
        //...
    }
}
class StreetFormatter{
    public toString(){
        // ...
    }
}
查看更多
可以哭但决不认输i
6楼-- · 2019-01-16 06:16

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.

查看更多
做个烂人
7楼-- · 2019-01-16 06:19

The answer by Aaron is the best and is working for me :) I would vote it up but sadly I can't (missing reputation).

I've to say testing private methods is the only way to use them and have clean code on the other side.

For example:

class Something {
  save(){
    const data = this.getAllUserData()
    if (this.validate(data))
      this.sendRequest(data)
  }
  private getAllUserData () {...}
  private validate(data) {...}
  private sendRequest(data) {...}
}

It' makes a lot of sense to not test all these methods at once because we would need to mock out those private methods, which we can't mock out because we can't access them. This means we need a lot of configuration for a unit test to test this as a whole.

This said the best way to test the method above with all dependencies is an end to end test, because here an integration test is needed, but the E2E test won't help you if you are practicing TDD (Test Driven Development), but testing any method will.

查看更多
登录 后发表回答