简体   繁体   English

在单元测试期间以angular2模拟定制服务

[英]Mock custom service in angular2 during unit test

I'm trying to write a unit test for component used in my service. 我正在尝试为我的服务中使用的组件编写单元测试。 Component and service work fine. 组件和服务工作正常。

Component: 零件:

import {Component} from '@angular/core';
import {PonyService} from '../../services';
import {Pony} from "../../models/pony.model";
@Component({
  selector: 'el-ponies',
  templateUrl: 'ponies.component.html',
  providers: [PonyService]
})
export class PoniesComponent {
  ponies: Array<Pony>;
  constructor(private ponyService: PonyService) {
    this.ponies = this.ponyService.getPonies(2);
  }
  refreshPonies() {
    this.ponies = this.ponyService.getPonies(3);
  }
}

Service: 服务:

import {Injectable} from "@angular/core";
import {Http} from "@angular/http";
import {Pony} from "../../models/pony.model";
@Injectable()
export class PonyService {
  constructor(private http: Http) {}
  getPonies(count: number): Array<Pony> {
    let toReturn: Array<Pony> = [];
    this.http.get('http://localhost:8080/js-backend/ponies')
    .subscribe(response => {
      response.json().forEach((tmp: Pony)=> { toReturn.push(tmp); });
      if (count && count % 2 === 0) { toReturn.splice(0, count); } 
      else { toReturn.splice(count); }
    });
    return toReturn;
  }}

Component unit test: 组件单元测试:

import {TestBed} from "@angular/core/testing";
import {PoniesComponent} from "./ponies.component";
import {PonyComponent} from "../pony/pony.component";
import {PonyService} from "../../services";
import {Pony} from "../../models/pony.model";
describe('Ponies component test', () => {
  let poniesComponent: PoniesComponent;
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [PoniesComponent, PonyComponent],
      providers: [{provide: PonyService, useClass: MockPonyService}]
    });
    poniesComponent = TestBed.createComponent(PoniesComponent).componentInstance;
  });
  it('should instantiate component', () => {
    expect(poniesComponent instanceof PoniesComponent).toBe(true, 'should create PoniesComponent');
  });
});

class MockPonyService {
  getPonies(count: number): Array<Pony> {
    let toReturn: Array<Pony> = [];
    if (count === 2) {
      toReturn.push(new Pony('Rainbow Dash', 'green'));
      toReturn.push(new Pony('Pinkie Pie', 'orange'));
    }
    if (count === 3) {
      toReturn.push(new Pony('Fluttershy', 'blue'));
      toReturn.push(new Pony('Rarity', 'purple'));
      toReturn.push(new Pony('Applejack', 'yellow'));
    }
    return toReturn;
  };
}

Part of package.json: package.json的一部分:

{
  ...
  "dependencies": {
    "@angular/core": "2.0.0",
    "@angular/http": "2.0.0",
    ...
  },
  "devDependencies": {
    "jasmine-core": "2.4.1",
    "karma": "1.2.0",
    "karma-jasmine": "1.0.2",
    "karma-phantomjs-launcher": "1.0.2",
    "phantomjs-prebuilt": "2.1.7",
    ...
  }
}

When I execute 'karma start' I get this error 当我执行'karma start'时,我得到了这个错误

Error: Error in ./PoniesComponent class PoniesComponent_Host - inline template:0:0 caused by: No provider for Http! 错误:./PoniesComponent类中的错误PoniesComponent_Host - 内联模板:0:0引起:没有Http的提供者! in config/karma-test-shim.js 在config / karma-test-shim.js中

It looks like karma uses PonyService instead of mocking it as MockPonyService , in spite of this line: providers: [{provide: PonyService, useClass: MockPonyService}] . 看起来karma使用PonyService而不是将其MockPonyServiceMockPonyService ,尽管这一行: providers: [{provide: PonyService, useClass: MockPonyService}]

The question: How I should mock the service? 问题:我应该如何嘲笑服务?

It's because of this 正因为如此

@Component({
  providers: [PonyService]  <======
})

This makes it so that the service is scoped to the component, which means that Angular will create it for each component, and also means that it supercedes any global providers configured at the module level. 这使得服务的范围限定为组件,这意味着Angular将为每个组件创建它,并且还意味着它取代在模块级别配置的任何全局提供者。 This includes the mock provider that you configure in the test bed. 这包括您在测试台中配置的模拟提供程序。

To get around this, Angular provides the TestBed.overrideComponent method, which allows us to override things like the @Component.providers and @Component.template . 为了解决这个问题,Angular提供了TestBed.overrideComponent方法,它允许我们覆盖@Component.providers@Component.template类的东西。

TestBed.configureTestingModule({
  declarations: [PoniesComponent, PonyComponent]
})
.overrideComponent(PoniesComponent, {
  set: {
    providers: [
      {provide: PonyService, useClass: MockPonyService}
    ]
  }
});

Another valid approach is to use tokens and rely on Intefaces instead of base classes or concrete classes, which dinosaurs like me love to do ( DIP , DI , and other SOLID Blablahs). 另一种有效的方法是使用令牌并依赖于Intefaces而不是基类或具体类,这些像我这样的恐龙喜欢做( DIPDI和其他SOLID Blablahs)。 And allow your component to have its dependencies injected instead of providing it yourself in your own component. 并允许您的组件注入其依赖项,而不是自己在您自己的组件中提供它。

Your component would not have any provider, it would receive the object as an interface in its constructor during angular's magic dependency injection. 你的组件没有任何提供者,它会在angular的 魔法 依赖注入期间在其构造函数中接收对象作为接口。 See @inject used in the constructor, and see the 'provide' value in providers as a text rather than a class. 请参阅构造函数中使用的@inject,并将提供程序中的“provide”值视为文本而不是类。

So, your component would change to something like: 因此,您的组件将更改为:

constructor(@Inject('PonyServiceInterface') private ponyService: IPonyService) {
   this.ponies = this.ponyService.getPonies(2); }

In your @Component part, you would remove the provider and add it to a parent component such as "app.component.ts". 在@Component部分中,您将删除提供程序并将其添加到父组件,例如“app.component.ts”。 There you would add a token: 在那里你会添加一个令牌:

providers: [{provide: 'PonyServiceInterface', useClass: PonyService}]

Your unit test component (the analog to app.component.ts) would have: providers: [{provide: 'PonyServiceInterface', useClass: MockPonyService}] 您的单元测试组件(类似于app.component.ts)将具有:providers:[{provide:'PonyServiceInterface',useClass:MockPonyService}]

So your component doesn't care what the service does, it just uses the interface, injected via the parent component (app.component.ts or your unit test component). 因此,您的组件不关心服务的作用,它只使用通过父组件(app.component.ts或您的单元测试组件)注入的接口。

FYI: The @inject approach is not very widely used, and at some point it looks like angular fellows prefer baseclasses to interfaces due to how the underlying javascript works. 仅供参考:@inject方法的使用并不是很广泛,在某些时候,由于底层的javascript工作方式,它看起来像有角色的人更喜欢基类到接口。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM