Mock custom service in angular2 during unit test

╄→гoц情女王★ 提交于 2019-12-09 08:09:09

问题


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:

{
  ...
  "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

Error: Error in ./PoniesComponent class PoniesComponent_Host - inline template:0:0 caused by: No provider for Http! in 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}].

The question: How I should mock the service?


回答1:


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. 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.

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



回答2:


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). 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. See @inject used in the constructor, and see the 'provide' value in providers as a text rather than a class.

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". 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}]

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).

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.



来源:https://stackoverflow.com/questions/40319045/mock-custom-service-in-angular2-during-unit-test

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!