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

ten*_*gen 1 unit-testing karma-runner angular

我想测试一个简单的Angular 2数据服务.该服务使用Http,但没有别的.在快速入门指南中,它说:

但是,使用不依赖于Angular的独立单元测试来探索应用程序类的内部逻辑通常会更有成效.这些测试通常更小,更易于阅读,编写和维护.

编写一个隔离单元测试的例子是,对于简单的服务,你可以通过在每个测试中创建一个新实例来测试服务...可能是这样的:

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

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

但是,我的EventDataService使用Http,所以如果我不将Http放在构造函数中,我会收到错误:

beforeEach(() => { service = new EventDataService(http: Http); });
Run Code Online (Sandbox Code Playgroud)

但是Http不存在,除非我导入它,我不想做 - 我不想测试Http.我尝试将http取消,但我尝试的所有方法最终都失败了或导致我导入更多东西以满足打字稿之神......

我敢肯定我在想这个.我已经尝试过很多关于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(); })
    }
};
Run Code Online (Sandbox Code Playgroud)

规格:

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!
Run Code Online (Sandbox Code Playgroud)

sno*_*ete 5

虽然我完全同意你应该在这个场景中使用TestBed来强制你的Http依赖关系的一般情绪(毕竟,这对Angular最初有依赖注入的原因来说是一个巨大的动力),我看到你的一些错误方法似乎表明存在一些误解.

您的EventDataService有一个构造函数,它需要一个类型为Http的参数.因此,只要您想手动创建EventDataService的实例,就必须使用构造函数并传递Http类型的单个参数.

所以,你应该这样做:

let dataService = new EventDataService(x);
Run Code Online (Sandbox Code Playgroud)

其中x是Http类型的变量.可能不太明显的是,Typescript不是像Java这样的语言 - 如果你想要的话,Typescript可以让你自己走开.所以,你可以,例如,只需创建一个新对象,并说它是'Http'类型,而Typescript编译器会假设你知道你正在做什么并让你继续.

所以你可以这样做:

let x = ({ } as Http);
let dataService = new EventDataService(x);
Run Code Online (Sandbox Code Playgroud)

第一行是告诉编译器我希望{}的类型是Http,而Typescript会让你不知所措,并假设你知道你在做什么.

因此,您可以使用该技术为您的测试获取Http的"实例" - 我非常松散地使用"实例"这个词.

但是,如果查看EventDataService,它会期望传递给其构造函数的http对象具有get返回可以调用map的对象的方法.换句话说,它期望get返回一个Observable.所以,如果你想伪造Http用于测试目的,你的假Http实例需要有一个get方法,并且get方法需要返回一个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!");
    });
Run Code Online (Sandbox Code Playgroud)

我会这样接近吗?可能不是 - 我只是使用TestBed来获取一个假的Http实例(不是因为这种方法很难,而是因为当你有多个依赖项时,TestBed简化了事情,而且服务似乎总是这样增长).但是在一天结束时,构造函数依赖注入(就像Angular使用的那样)很容易理解 - 将依赖项(伪造或真实)作为参数传递给您尝试创建实例的类的构造函数.

  • 谢谢你——这正是我所需要的。另外,我很欣赏你在回答我的实际问题的同时外交上不同意我的做法的方式。如果我能不止一次地对此投赞成票,我会的。谢谢你! (2认同)