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:23

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.

查看更多
叼着烟拽天下
3楼-- · 2019-01-16 06:24

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.

查看更多
混吃等死
4楼-- · 2019-01-16 06:34

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]);
});
查看更多
登录 后发表回答