如何用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
-
将testing代码本身放在闭包中,或者在闭包中添加代码,将外部作用域中现有对象上的局部variables的引用存储在闭包中。
之后用工具去掉testing代码。 http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/
如果你做了什么,请给我build议一个更好的方法来解决这个问题?
PS
-
大多数类似这样的问题的答案不能解决问题,这就是为什么我问这个问题
-
大多数开发者说你不要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使用private
, protected
, public
限制对类成员的访问,但是编译后的JS没有私有成员,因为这不是JS中的事情。 它纯粹用于TS编译器。 为此:
-
你可以断言
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
-
你可以使用数组访问(
[]
)来获得私有成员: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它们。 这通常意味着:
- 您正试图testing实现而不是API(公共接口)。
- 您应该将所讨论的逻辑移到自己的类中,以使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]); });