Angular 2 在不使用 Testbed 的情况下测试注入 Http 的服务

Angular 2 Testing a service which injects Http without using Testbed

我想测试一个简单的 Angular 2 数据服务。该服务使用 Http,但仅此而已。在 quickstart guide 它说:

However, it's often more productive to explore the inner logic of application classes with isolated unit tests that don't depend upon Angular. Such tests are often smaller and easier to read, write, and maintain.

它给出的编写独立单元测试的示例是,对于简单的服务,您可以通过在每个测试中创建它的新实例来测试服务......可能是这样的:

beforeEach(() => { service = new EventDataService(); });

it('#getEvents should return an observable', () => {
    expect(service.getEvents()).toBe(Observable.from([]);
});

但是,我的 EventDataService 使用 Http,所以如果我不像这样将 Http 放入构造函数中,我会收到错误消息:

beforeEach(() => { service = new EventDataService(http: Http); });

但是 Http 不存在,除非我导入它,我不想这样做 - 我不想测试 Http。我尝试将 http 存根,但我尝试过的所有方法都以失败告终,或者导致我导入更多东西来满足 Typescript 之神...

我确定我想多了。我已经在许多讨论 Angular 2 中的测试的网站上尝试了这些建议,但我怀疑任何超过几个月的内容,因为框架在过去 6-12 个月内发生了很大变化。我觉得我应该能够为这样一个简单的例子保持简单。

我是不是做错了什么? 我正在使用 Angular 2 V 2.4.10、Webpack 2.3.1、Sinon 2.1.0 和 Typescript 2.2.1。

服务:

import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { Observable } from "rxjs";
import { Event } from "../event/event.interface";

@Injectable()
export class EventDataService {
    events: Event[];

    constructor(private http: Http) { }

    getEvents(): Observable<Event[]> {
        return this.http.get("api/events")
        .map((response) => {return response.json(); })
    }
};

规格:

import { Http } from "@angular/http";
import { EventDataService } from "./event-data.service";
import * as sinon from "sinon";
import { expect } from "chai";

describe("Event Data Service", () => {
    it("GetEvents", () => {
        sinon.stub(Http, "get").returns(Promise.resolve("sinon Event!"));
        let eventDataService = new EventDataService();

        expect(eventDataService.getEvents()).to.equal("sinon event!");;
    });
});

Thank you!

您应该需要创建一个测试模块并模拟响应以测试在您的测试模块中提供依赖项的服务方法

 import { async, getTestBed, TestBed, inject } from "@angular/core/testing";
 import { Response, ResponseOptions, HttpModule, XHRBackend } from "@angular/http";
 import { MockBackend, MockConnection } from "@angular/http/testing";
 import { EventDataService } from "./event-data.service";

 describe("EventDataService", () => {
  let mockBackend: MockBackend;
  let service: EventDataService;
  let injector: Injector;

  beforeEach(() => {
   TestBed.configureTestingModule({
     imports: [HttpModule],
      providers: [
      { provide: XHRBackend, useClass: MockBackend },
      EventDataService
      ]
    });

    injector = getTestBed();
  });

  beforeEach(() => {
    mockBackend = injector.get(XHRBackend);
    service = injector.get(EventDataService);
  });

尽管我完全同意在这种情况下您应该使用 TestBed 来消除 Http 依赖的普遍观点(毕竟,这是 Angular 首先进行依赖注入的一个巨大动力) ,我在您的方法中发现了一些错误,这似乎表明存在一些误解。

您的 EventDataService 有一个构造函数,它需要一个 Http 类型的参数。因此,每当您想手动创建 EventDataService 的实例时,都必须使用构造函数并传递一个类型为 Http 的参数。

所以,你应该这样做:

let dataService = new EventDataService(x);

其中 x 是 Http 类型的变量。可能不太明显的是,Typescript 不是一种像 Java 这样的语言——如果你愿意,Typescript 可以让开你的路。因此,例如,您可以只创建一个新对象,并说它是 'Http' 类型,Typescript 编译器会假设您知道自己在做什么并让您继续。

所以你可以这样做:

let x = ({ } as Http);
let dataService = new EventDataService(x);

第一行是告诉编译器我希望 {} 的类型为 Http,Typescript 将让开并假设您知道自己在做什么。

因此,您可以使用该技术获得一个 'instance' 的 Http 用于您的测试 - 我对 'instance' 这个词的使用非常随意。

但是,如果您查看您的 EventDataService,它希望传递给其构造函数的 http 对象具有一个 get 方法,该方法 return 是一个您可以在其上调用映射的对象.换句话说,它期望 get 到 return 一个 Observable。所以,如果你想为了测试目的伪造 Http,你的伪 Http 实例需要有一个 get 方法,并且那个 get 方法需要 return 一个 Observable。

将以上所有内容放在一起,如果我想在不使用 TestBed 的情况下编写自己的测试,我最终会得到:

    let fakeHttp = {
      get: (_: any) => {}
    };

    // I'm not familiar with sinon,
    // but i believe this is stubbing the get method of fakeHttp
    // and returning a canned response
    sinon.stub(fakeHttp, "get").returns(Observable.of("sinon Event!"));

    let eventDataService = new EventDataService(fakeHttp);

    // remember, getEvents returns an observable,
    // so to test it you have to subscribe to it and check its values
    eventDataService.getEvents().subscribe(data => {
      expect(data).to.equal("sinon Event!");
    });

我会这样处理吗?可能不是——我只是使用 TestBed 来获取一个假的 Http 实例(不是因为这种方法有那么困难,而是因为当你有多个依赖项时,TestBed 会简化事情,而且服务似乎总是以这种方式增长)。但归根结底,构造函数依赖注入,就像 Angular 使用的一样,非常容易理解 - 将您的依赖项(假的或真实的)作为参数传递给 class 的构造函数试图创建一个实例。