如何在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;
    }

}

我找到了解决办法

将测试代码本身放在闭包中,或者在闭包中添加代码,以存储外部作用域中现有对象上局部变量的引用。 稍后使用工具提取测试代码。 http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/

如果你做过这个问题,请给我一个更好的解决方法。

P.S

大多数类似类型的问题的答案都没有给出问题的解决方案,这就是我问这个问题的原因 大多数开发人员说不要测试私有函数,但我不会说它们是错的还是对的,但我的案例中有必要测试私有函数。


当前回答

不要为私有方法编写测试。这就破坏了单元测试的意义。

您应该测试类的公共API 你不应该测试类的实现细节

例子

class SomeClass {

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

如果之后实现发生变化,但公共API的行为保持不变,则该方法的测试不需要更改。

class SomeClass {

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

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

不要仅仅为了测试而将方法和属性设为公共。这通常意味着:

您正在尝试测试实现,而不是API(公共接口)。 您应该将所讨论的逻辑移到它自己的类中,以使测试更容易。

其他回答

我同意@toskv的观点:我不建议这么做:-)

但是如果你真的想测试你的私有方法,你可以意识到TypeScript的相应代码对应于构造函数原型的一个方法。这意味着它可以在运行时使用(然而您可能会有一些编译错误)。

例如:

export class FooBar {
  private _status: number;

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

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

将转化为:

(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);

查看这个plunkr: https://plnkr.co/edit/calJCF?p=preview。

不要为私有方法编写测试。这就破坏了单元测试的意义。

您应该测试类的公共API 你不应该测试类的实现细节

例子

class SomeClass {

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

如果之后实现发生变化,但公共API的行为保持不变,则该方法的测试不需要更改。

class SomeClass {

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

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

不要仅仅为了测试而将方法和属性设为公共。这通常意味着:

您正在尝试测试实现,而不是API(公共接口)。 您应该将所讨论的逻辑移到它自己的类中,以使测试更容易。

你可以调用私有方法!

如果您遇到以下错误:

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

只需使用// @ts-ignore:

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

感谢@Moff452的评论。你也可以这样写:

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

更新:

@ts-expect-error是@ts-ignore的更好选择。看到的: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#ts-ignore-or-ts-expect-error

这招对我很管用:

而不是:

sut.myPrivateMethod();

这样的:

sut['myPrivateMethod']();

“不要测试私有方法”的重点实际上是像使用它的人那样测试类。

如果你有一个带有5个方法的公共API,你的类的任何消费者都可以使用这些方法,因此你应该测试它们。使用者不应该访问类的私有方法/属性,这意味着在公共公开功能保持不变的情况下,可以更改私有成员。


如果依赖内部可扩展功能,请使用protected而不是private。 注意protected仍然是一个公共API(!),只是使用方式不同。

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;
    }
}

单元测试保护的属性与消费者使用它们的方式相同,通过子类化:

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]);
});