Angular 2 - 模拟私有财产

时间:2017-02-15 21:55:27

标签: angular typescript

虽然我理解测试代码的理想方式是以与生产相同的方式使用它,因此不直接处理私有属性和方法TypeScript让我有点沮丧。

我有一个用户服务。

// user.service.ts
import {Injectable} from '@angular/core';
import {AppHttpService} from '../app-http/app-http.service'
@Injectable()
export class UserService {

  constructor(private appHttp: AppHttpService) {
  }
}

如图所示,它取决于具有私有属性和方法的appHttp服务,让我们说看起来像这样:

// app-http.service.ts
@Injectable()
export class AppHttpService {
  private apiUrl     = 'my domain';
  constructor(private http: Http, private authHttp: AuthHttp) {
  }

  post(body): Observable<any> {
     return this.http.post(this.apiUrl, body)
        .map((res)=>res)
        .catch((err)=>null);
  }
}

为了对我的用户服务运行一个独立的测试,我想把它简单地模仿我的appHttp服务。不幸的是,如果我只是模拟公共方法,appHttp的属性并将其提供给我的构造函数,如下所示:

// user.service.spec.ts
describe('', () => {
  let appHttpMock = {
    post: jasmine.createSpy('post')
  };
  let service = new UserService(appHttpMock);
  beforeEach(() => {
  })
  it('', () => {
  })
})

我收到错误声明:

Error:(11, 33) TS2345:Argument of type '{ post: Spy; }' is not assignable to parameter of type 'AppHttpService'.  Property 'apiUrl' is missing in type '{ post: Spy; }'.

如果我改变我的模拟只是添加属性我会得到另一个错误抱怨它不是私人的。如果我创建一个真实的模拟类,如:

// app-http.mock.ts
export class AppHttpMockService {
  private apiUrl     = 'my domain';

  constructor() {
  }

  post() {
  }
}

我还会收到另一个TypeScript错误:

Error:(8, 33) TS2345:Argument of type 'AppHttpMockService' is not assignable to parameter of type 'AppHttpService'. Types have separate declarations of a private property 'apiUrl'.

运行独立测试的简洁方法是什么(即,不需要耗费时间创建测试平台的测试)没有TypeScript在模拟的私有属性和方法上烦恼?

3 个答案:

答案 0 :(得分:4)

TL; DR:如果它导致可测性问题,请避免使用private,因为它确实很少。

作为TypeScript的忠实拥护者和该语言的拥护者,我对private关键字没有多大用处。

它有一些问题。

它不会限制在运行时对字段的实际访问,它只用于限制抽象的公共API表面。

显然,大多数TypeScript的好处只是编译时间(顺便说一句,这是一件好事)所以这不是针对该功能的直接观点,但我觉得它给开发人员带来了错误的封装感,更有问题的是,鼓励他们避免尝试和真正的JavaScript技术,这些技术实际上通过闭包来强制执行隐私。

现在我知道如果你正在使用类,通过闭包的封装范围从笨拙到不可能,但如果你真的想要一个私有字段,当然如果你想要一个私有方法,你可以声明一个外部的函数和不从封闭模块中导出它。这为您提供了真正的隐私。

还有另一个问题。 ECMAScript在实际的私有属性中有一个提议,并且几乎可以保证使用不同的语法并且具有不同的语义。

基本上TypeScript的private概念具有非常弱的语义,并且在真正的私有属性是ECMAScript的一部分时将成为语法和语义冲突,所以不要在哪里使用它这是有问题的。

我并不是说完全避免它,但在这种情况下,删除修饰符是最简单的解决方案。

还有许多可变状态,私有或其他,是不可取的。有时需要对事物进行优化,但您需要首先测量它是否能在特定情况下帮助您。

通常情况下,可以定义类属性的最佳方法是get,但没有对应的set

答案 1 :(得分:2)

在这种情况下,您可以使用接口:

export interface IAppHttpService  {post(body):Observable<any>;}

export class AppHttpService implements IAppHttpService { ... } 

export class UserService {
  constructor(@Inject(AppHttpService) private appHttp: IAppHttpService) {
  }
}

答案 2 :(得分:1)

我有类似的问题,并且找到了此解决方案(不使用jasmine.createSpy):

import { Service } from 'my-service';

class ServiceMock {
    service(param: string): void { // do mocking things }
}

function factoryServiceMock(): any {
    return new ServiceMock();
}

// user.service.spec.ts
describe('', () => {
    const serviceMocked = factoryServiceMock() as Service;
    const userService = new UserService(serviceMocked);
    beforeEach(() => {
    });
    it('', () => {
    });
});

我不知道这是否是最好的解决方案,但它适用于模拟策略,显然不使用Testbed