如何用Jasmine为私有方法编写Angular 2 / TypeScript的unit testing

你如何testingangular2的私人function?

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

我find的解决scheme

  1. 将testing代码本身放在闭包中,或者在闭包中添加代码,将外部作用域中现有对象上的局部variables的引用存储在闭包中。

    之后用工具去掉testing代码。 http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/

如果你做了什么,请给我build议一个更好的方法来解决这个问题?

PS

  1. 大多数类似这样的问题的答案不能解决问题,这就是为什么我问这个问题

  2. 大多数开发者说你不要testing私有函数,但是我不认为它们是错误的或者正确的,但是我的情况是需要testing私有的。

我和你在一起,即使“仅对公共API进行unit testing”这个目标很好,但有时候看起来并不那么简单,你觉得你是在selectAPI还是unit testing。 你已经知道了,因为这正是你要求做的,所以我不会进入它。 🙂

在TypeScript中,为了进行unit testing,我发现了几种访问私有成员的方法。 考虑这个类:

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

尽pipeTS使用privateprotectedpublic限制对类成员的访问,但是编译后的JS没有私有成员,因为这不是JS中的事情。 它纯粹用于TS编译器。 为此:

  1. 你可以断言any并逃避编译器警告你有关访问限制:

     (thing as any)._name = "Unit Test"; (thing as any)._count = 123; (thing as any).init("Unit Test", 123); 

    这种方法的问题是,编译器根本不知道你在做什么的权利,所以你不会得到所需的types错误:

     (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. 你可以使用数组访问( [] )来获得私有成员:

     thing["_name"] = "Unit Test"; thing["_count"] = 123; thing["init"]("Unit Test", 123); 

    虽然看起来很时髦,但TSC实际上会validationtypes,就像直接访问它们一样:

     thing["_name"] = 123; // type error thing["_count"] = "Unit Test"; // type error thing["init"](0, "123"); // argument error 

    说实话,我不知道为什么这个工程。 似乎数组括号不强制访问限制,但types推断给你完整的types安全。 这正是我想为你的unit testing所需要的。

这是TypeScript Playground中的一个工作示例 。

由于大多数开发人员不build议testing私有函数 ,为什么不testing它呢?

例如。

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

感谢@Aaron @Thierry Templier。

不要为私有方法编写testing。 这打破了unit testing的重点。

  • 你应该testing你的类的公共API
  • 你不应该testing你的class级的implimentation细节

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

如果稍后实现更改但公共API的behaviour保持不变,则不需要更改此方法的testing。

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

不要公开方法和属性来testing它们。 这通常意味着:

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

我同意@toskv: 我不会推荐这样做 🙂

但是如果你真的想testing你的私有方法,你可以意识到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); 

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

“不要testing私有方法”的观点实际上就是像使用它的人一样testing这个类

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


如果您依赖内部可扩展function,请使用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; } } 

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

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