Angular 2使用observable测试组件而不模拟

时间:2017-04-18 17:57:40

标签: angular typescript jasmine integration-testing observable

我一直在尝试使用Jasmine为Angular 2项目创建集成测试。我可以很好地模拟服务和数据并运行相应的单元测试,但现在我想测试以确保我的组件可以使用该服务实际连接到后端API。到目前为止,我可以确认我的组件和服务中的方法是从我的间谍中触发的,但是我的组件中的.subscribe(....)方法在测试完成/失败之后才会完成。我的规范代码如下:

import { async, ComponentFixture, TestBed, inject, fakeAsync } from '@angular/core/testing';
import { DebugElement } from "@angular/core";
import { By } from "@angular/platform-browser";

import { ReviewComponent } from './review.component';
import { FormsModule } from '@angular/forms';
import { UrlService } from 'app/shared/services/url.service';
import { HttpModule, Http } from '@angular/http';
import { ReviewService } from "./review.service";

describe('CommunicationLogComponent', () => {
  let component: ReviewComponent;
  let fixture: ComponentFixture<ReviewComponent>;
  let serviceSpy: jasmine.Spy;
  let componentSpy: jasmine.Spy;
  let service: ReviewService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [FormsModule, HttpModule],
      providers: [UrlService],
      declarations: [ReviewComponent]
    })
      .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ReviewComponent);
    component = fixture.componentInstance;
    component.processId = 6;
    service = fixture.debugElement.injector.get(ReviewService);
    componentSpy = spyOn(component, "ngOnInit").and.callThrough();
    serviceSpy = spyOn(service, 'getReviewByProcess').and.callThrough();
  });

  it('should create component', () => {
    expect(component).toBeTruthy();
  });
  it('should not have called ngOnInit() yet', () => {
    expect(componentSpy).not.toHaveBeenCalled();
  });

  it('should make a call to ngOnInit() and by extension getReviewByProcess', () => {
    fixture.detectChanges();
    expect(componentSpy).toHaveBeenCalled();
    expect(serviceSpy).toHaveBeenCalled();
  });
  //ISSUES HERE
  it('should show review after the getReviewByProcess resolves', async(() => {
    fixture.detectChanges();
    fixture.whenStable().then(() => {
      expect(componentSpy).toHaveBeenCalled();
      fixture.detectChanges();
      expect(component.Reviews[0]).not.toBe(undefined);
    });
  }), 5000);        
});

所有测试直到标有“// ISSUES HERE”的测试工作正常并通过。

在尝试完成测试之前,我正在使用async()并等待fixture变得稳定。它还通过componentSpy确认已经调用了ngOnInit(它将连接到后端并订阅评论)。

我的组件代码如下所示:

import { Component, EventEmitter, Input, OnInit } from '@angular/core';

//Service 
import { ReviewService } from "app/process/monitoring/shared/components/review/review.service";

//Classes
import { Review } from "app/process/monitoring/shared/components/review/review";

@Component({
  providers: [
    ReviewService
  ],
  selector: 'monitoring-review',
  templateUrl: './review.component.html',
  styleUrls: ['./review.component.css']
})
export class ReviewComponent implements OnInit {

  public Review: Review = new Review();
  public Reviews: Review[] = [];

  @Input() public processId: number;

  constructor(
    private ReviewService: ReviewService
  ) { }


  ngOnInit(): void {
    this.ReviewService.getReviewByProcess(this.processId, true).first().subscribe(
      Reviews => {
        if (Reviews) {
          this.Reviews = Reviews //doesn't trigger until test is finished
        }
      },
      error => console.log(error)
    );
  }
}

最后,我的ReviewService看起来像这样:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Headers, RequestOptions, RequestMethod, URLSearchParams } from '@angular/http';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

// App-wide Services
import { UrlService } from 'app/shared/services/url.service';

// Classes
import { Review } from "app/process/monitoring/shared/components/review/review";


@Injectable()
export class ReviewService {

  private devUrl: string;
  private jsonHeaders: Headers = new Headers({ 'Content-Type': 'application/json' });
  private prodUrl: string;
  private reviewPath: string = 'monitoring/program/';

  constructor(
    private urlService: UrlService,
    private http: Http
  ) {
    this.devUrl = this.urlService.getUrl('dev').url;
    this.prodUrl = this.urlService.getUrl('prod').url;
  }

  getReviewByProcess(processId: number, isProd: boolean = false): Observable<Review[]> {
    let targetUrl: string = (isProd ? this.prodUrl : this.devUrl) + this.reviewPath + "/" + processId;

    return this.http.get(targetUrl, { withCredentials: true })
      .map((res: Response) => res.json())
      .catch((error: any) => Observable.throw(error.json().error || 'Server error')
      );
  };
}

使用Karma调试器,我可以确认我的测试调用ngOnInit,然后调用该服务,但它永远不会命中我的组件的内部部分(标记为“//不触发”),直到我的测试完成后。

总结:我想创建一个测试,可以使用“实时”后端测试我的组件与我的服务集成以收集我的数据。这是服务,我的组件不会嘲笑任何东西。我可以确认我的方法已经到达,但在我的ReviewComponent的ngOnInit()方法中找到的.subscribe(...)在测试完成/失败之后才会完成。

2 个答案:

答案 0 :(得分:2)

您需要返回observable并在测试中订阅它以使异步方法等待它。例如,以下假设测试将起作用。

it('', async(() => {
  observable().subscribe(
    (review) => {
       expect(component.Reviews[0]).not.toBe(undefined)
    }
  )
}));

问题是你没有等待在测试中执行异步调用。你可以稍微改变角度http服务,即:

import { Http, HttpModule, BaseRequestOptions, XHRBackend, HttpModule, ConnectionBackend } from '@angular/http';
const httpSubject = new Subject()
const http$ = httpSubject.asObservable()
const httpFactory = (backend, options) => {
  const newHttp = new Http(backend, options);
  const helperHttp = new Http(backend, options);
  newHttp.get = function(strng, options) {
    return helperHttp.get(strng, options).do((response) => {
      setTimeout(() => {
        httpSubject.next({});
      }, 300);
    });
  };
  return newHttp;
}

TestBed.configureTestingModule({
  imports: [HttpModule, FormModule],
  providers: [
    UrlService,
    ConnectionBackend,
    BaseRequestOptions
    {
      provide: Http,
      useFactory: httpFactory,
      deps: [ XHRBackend, BaseRequestOptions] 
    }
  ],
  declarations: [ReviewComponent]
})

it('should show review after the getReviewByProcess resolves', 
  async(() => {
    http$.subscribe(() => {
      expect(componentSpy).toHaveBeenCalled();
      expect(component.Reviews[0]).not.toBe(undefined);
    })
    fixture.detectChanges();
})); 

答案 1 :(得分:0)

您需要服务才能返回模拟数据。这样的事情:

spyOn(service, 'getReviewByProcess')
   .and.returnValue(Observable.of(new Array<Review>()));