【问题标题】:Error in test when mocking service with Observable (Angular 2)使用 Observable (Angular 2) 模拟服务时测试出错
【发布时间】:2017-07-01 13:14:51
【问题描述】:

我有一个使用返回 Observable 的服务的组件。我没有在 Jasmine 测试中连接此服务,而是选择监视模拟。

这是使用 Http 从 Web 服务器获取 JSON 响应的 NumProbesService [numprobes.service.ts]:

import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import {Observable} from 'rxjs/Rx';
import 'rxjs/add/operator/map'
import {ProbeCount} from "./probecount.model";

@Injectable()
export class NumProbesService {

  private numProbesUrl = 'http://localhost:9090/getnumberofprobes';
  private probeCount: ProbeCount;

  constructor (private http: Http) {}

  createAuthorizationHeader(headers: Headers) {
    headers.append('X-Auth-Key', 'mjones');
    headers.append('X-Auth-Secret', '111111-2222222-22222-3233-4444444');
  }


  public getProbeCount() : Observable<ProbeCount> {
    let headers = new Headers();
    this.createAuthorizationHeader(headers);

    return this.http.get(this.numProbesUrl, {headers: headers})
      .map((response:Response) =>  this.probeCount = <ProbeCount>response.json())
      .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
  }

}

我正在使用 NumProbesMockService [numprobes.service.mock.ts] 模拟此服务:

import { Observable } from 'rxjs/Observable';
import { ProbeInfo } from './probeinfo.model';

export class NumProbesMockService {

  probeInfo : ProbeInfo = new ProbeInfo(3);

  public getProbeCount(): Observable<ProbeInfo> {
    return Observable.of(this.probeInfo);
  }

}

ProbeInfo [probeinfo.model.ts] 类在这里:

export class ProbeInfo {

  private online : boolean;
  private accepted: boolean;
  private probeCount: number;

  constructor(probeCount: number) {

  }

}

我正在测试的组件在这里:

import {Component, Input} from '@angular/core';
import {NumProbesService} from './numprobes.service';
import {ProbeCount} from "./probecount.model";

@Component({
  selector: 'numprobes-box',
  templateUrl: './numprobes.component.html'
})
export class NumProbesComponent {

  name: string;
  numprobes: number;
  probeCount: ProbeCount;

  constructor(private numProbesService: NumProbesService) {

  }

  ngOnInit() {
    this.name = "Number of Probes";

    this.numProbesService.getProbeCount().subscribe(
      (probeCount) => {
        console.log("probeCount: " + JSON.stringify(probeCount));
        console.log(probeCount.total_probe_count);
        this.numprobes = probeCount.total_probe_count;
      }
    );
  }
}

最后,这是对组件的实际测试。

import {By} from '@angular/platform-browser';
import {DebugElement} from '@angular/core';

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {NumProbesService} from './numprobes.service';
import {NumProbesMockService} from './numprobes.service.mock';
import {NumProbesComponent} from './numprobes.component';
import {ProbeInfo} from './probeinfo.model';


describe('NumProbesComponent', () => {

  let comp: NumProbesComponent;
  let fixture: ComponentFixture<NumProbesComponent>;
  let spy: jasmine.Spy;
  let de: DebugElement;
  let el: HTMLElement;
  let numProbesService: NumProbesService; // the actually injected service

  const numProbes = 5;
  let probeInfo : ProbeInfo = new ProbeInfo(numProbes);


  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [NumProbesComponent],
      providers: [
        { provide: NumProbesService, useClass: NumProbesMockService }
      ]
    });


    fixture = TestBed.createComponent(NumProbesComponent);
    comp = fixture.componentInstance;

    numProbesService = fixture.debugElement.injector.get(NumProbesService);

    spy = spyOn(numProbesService, 'getProbeCount')
      .and.returnValue(probeInfo);
  });

  it('Should show the label within component', () => {

    de = fixture.debugElement.query(By.css(".info-box-text"));
    el = de.nativeElement;

    fixture.detectChanges();
    expect(el.textContent).toBe('Number of Probes', 'Label displayed');

  });


  it('should show the name of the info box, "Number of Probes"', () => {

    de = fixture.debugElement.query(By.css(".info-box-number"));
    el = de.nativeElement;

    console.log("el.textContent: " + el.textContent);

    expect(el).toBeDefined();
    expect(el.textContent).toBe('', 'nothing displayed');

    let probeInfoCalled = numProbesService.getProbeCount();

    expect(spy.calls.any()).toBe(true, 'getProbeCount not yet called');

  });
}

这让我想到了这个问题。我的一项测试失败了。在fixture.detectChange() 之后,看起来组件已初始化并且getProbeCount() 在模拟服务NumProbesMockService 上执行。

上面写着this.numProbesService.getProbeCount(...).subscribe is not a function。怎么可能? this.numProbesService.getProbeCount() 返回一个 Observable,它有一个 subscribe 方法,对吧?

这是完整的错误。任何帮助将不胜感激。

Chrome 56.0.2924 (Mac OS X 10.10.5) NumProbesComponent Should show the label within component FAILED
    Error: Error in :0:0 caused by: this.numProbesService.getProbeCount(...).subscribe is not a function
        at ViewWrappedError.ZoneAwareError (webpack:///~/zone.js/dist/zone.js:811:0 <- src/test.ts:106351:33)
        at ViewWrappedError.BaseError [as constructor] (webpack:///~/@angular/core/src/facade/errors.js:26:0 <- src/test.ts:6476:16)
        at ViewWrappedError.WrappedError [as constructor] (webpack:///~/@angular/core/src/facade/errors.js:88:0 <- src/test.ts:6538:16)
        at new ViewWrappedError (webpack:///~/@angular/core/src/linker/errors.js:73:0 <- src/test.ts:60069:16)
        at CompiledTemplate.proxyViewClass.DebugAppView._rethrowWithContext (webpack:///~/@angular/core/src/linker/view.js:650:0 <- src/test.ts:84195:23)
        at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (webpack:///~/@angular/core/src/linker/view.js:623:0 <- src/test.ts:84168:18)
        at ViewRef_.detectChanges (webpack:///~/@angular/core/src/linker/view_ref.js:179:0 <- src/test.ts:61015:20)
        at ComponentFixture._tick (webpack:///~/@angular/core/bundles/core-testing.umd.js:191:0 <- src/test.ts:12899:36)
        at webpack:///~/@angular/core/bundles/core-testing.umd.js:205:45 <- src/test.ts:12913:53
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:242:0 <- src/test.ts:105782:26)
        at ProxyZoneSpec.onInvoke (webpack:///~/zone.js/dist/proxy.js:79:0 <- src/test.ts:71475:39)
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:241:0 <- src/test.ts:105781:32)
        at Object.onInvoke (webpack:///~/@angular/core/src/zone/ng_zone.js:269:0 <- src/test.ts:31712:37)
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:241:0 <- src/test.ts:105781:32)
        at Zone.run (webpack:///~/zone.js/dist/zone.js:113:0 <- src/test.ts:105653:43)

【问题讨论】:

  • 如果您在订阅之前 console.log numProbesService.getProbeCount 会看到什么?旁注:似乎你的间谍和模拟两次做同样的事情:既然你的模拟已经返回模拟数据,为什么你需要间谍也返回模拟数据?
  • @AngularFrance,我在调用 subscribe() 之前将console.log(this.numProbesService.getProbeCount()); 添加到组件并得到LOG: ProbeInfo{}。这是否意味着当调用 getProbeCount() 时我没有从我的模拟服务中获得 Observable?该方法显然返回Observable&lt;ProbeInfo&gt;。你知道我哪里可能出错了吗?
  • 您能告诉我您要在测试中验证什么吗? (不提你是怎么做的,我很想知道你在做什么)
  • @AngularFrance,我正在尝试验证 1)作为组件属性的信息框的标签出现在信息框 html 中,以及 2)探测器的数量(属性ProbeCount 对象)与模拟服务返回的数字匹配。

标签: unit-testing angular jasmine observable


【解决方案1】:

您得到的错误是由于 spy 覆盖了原始方法

也就是说,在这行执行之后:

spyOn(numProbesService, 'getProbeCount')

numProbesService.getProbeCount 不再引用返回 Observable 的原始方法,它引用了 spy。

我设置了Plunker 来测试我的答案,它按预期工作:如果您注释掉间谍,您将能够再次订阅:

// app.component.spec.ts

beforeEach( async(() => {
  ps = fixture.debugElement.injector.get(ProbesService);

  // The spy overrides the ps.getProbeCount() method.
  //spyOn(ps, 'getProbeCount');

  // trigger initial data binding
  fixture.detectChanges();
}));

it('should return 5', (done) => {
  // This will NOT work if the spy has been set up.
  ps.getProbeCount().subscribe(val => {
    expect(val).toEqual(5);
    done();
  });
});

it('should have been called', () => {
  // This will work ONLY IF the spy has been set up.
  expect(ps.getProbeCount).toHaveBeenCalled();
});

但就像我在评论中所说的那样,您做了两次同样的事情。使用 spy 或 mock 来返回 mock 值,但不能同时使用。

【讨论】:

  • 确实如此!做到了。我既是间谍又是嘲笑,我应该做其中之一。我摆脱了间谍,只使用了模拟,一切正常。
  • 太棒了。如果您不介意,请将答案标记为“正确”,以便 StackOverflow 上的其他人将来可以使用它。谢谢,梅尔文!
猜你喜欢
  • 2017-02-16
  • 2017-09-25
  • 2017-09-14
  • 2018-09-28
  • 1970-01-01
  • 1970-01-01
  • 2017-08-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多