使用Jest测试使用Observable.forkJoin的Angular方法

时间:2018-09-13 21:54:34

标签: angular unit-testing observable jestjs jest-preset-angular

我正在尝试为使用Observable.forkJoin的组件方法编写测试。我到处都是网络,在大理石弹珠洞中钻了很多孔,但是最后,我认为我真正需要做的就是模拟Observable forkJoin调用并返回假数据。这是我的组件方法

  public loadData(): void {
    this.someOtherMethod();
    this.someProperty = false;
    this.someOtherMethod2();

    if (this.isNew) {
      this.noData = true;
    } else if (this.key) {
      Observable.forkJoin([
        /*00*/ this.service1$.someCall(this.key),
        /*01*/ this.service2$.someCall(this.key),
        /*02*/ this.service2$.someCall1(this.key),
        /*03*/ this.service2$.someCall2(this.key),
        /*04*/ this.service2$.someCall3(this.key),
        /*05*/ this.service2$.someCall4(this.key),
        /*06*/ this.service2$.someCall5(this.key),
      ])
        .takeWhile(() => this.alive)
        .subscribe(
          response => {
            ... // join all the data together
          },
          // error => this.handleError(error)
        );
    }

    this.changeDetector$.markForCheck();
  }

到目前为止,这是我的测试:

it('makes expected calls', async(() => {
  const response = [];
  const service1Stub: Service1 = fixture.debugElement.injector.get(Service1 );
  const service2Stub: Service2 = fixture.debugElement.injector.get(Service2 );

  comp.key = key;
  spyOn(comp, 'someOtherMethod').and.returnValue(of(response));
  spyOn(comp, 'someOtherMethod2').and.returnValue(of(dummyData));

  spyOn(service1Stub, 'someCall').and.returnValue(of(dummyData));
  spyOn(service2Stub, 'someCall').and.returnValue(of(response));
  spyOn(service2Stub, 'someCall1').and.returnValue(of(response));
  spyOn(service2Stub, 'someCall2').and.returnValue(of(response));
  spyOn(service2Stub, 'someCall3').and.returnValue(of(response));
  spyOn(service2Stub, 'someCall4').and.returnValue(of(response));
  spyOn(service2Stub, 'someCall5').and.returnValue(of(response));

  comp.loadData();
  expect(comp.someProperty).toBe(false);
  expect(comp.someOtherMethod).toHaveBeenCalled();
  expect(comp.someOtherMethod2).toHaveBeenCalled();
  expect(service1Stub.someCall).toHaveBeenCalledWith(key);
  expect(service2Stub.someCall1).toHaveBeenCalledWith(key);                  
  expect(service2Stub.someCall2).toHaveBeenCalledWith(key);
  expect(service1Stub.someCall3).toHaveBeenCalledWith(key);
  expect(service1Stub.someCall4).toHaveBeenCalledWith(key);
  expect(service1Stub.someCall5).toHaveBeenCalledWith(key);
}));

我收到以下错误(在注释掉上面的错误捕获之后):

TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

大理石似乎与测试可观察物及其反应方式有关。我只想看看正在进行调用,并对订阅中所有数据连接在一起的内部事件进行更深入的测试。

我知道有更好的方法来处理数据,但这需要对应用程序进行大修。我无法更改方法,只能忍受现在的情况。

2 个答案:

答案 0 :(得分:0)

这里有些笨拙,它在复制过程中对我有用:

import { ComponentFixture, TestBed, } from '@angular/core/testing';
import { Component, Injectable, OnInit } from '@angular/core';
import { of, forkJoin } from 'rxjs';

@Injectable({providedIn: 'root'})
export class Service { // service to be mocked
  response = ['hello', 'world'];
  // this is mocked anyway
  someCall() { return of(this.response); }
  someCall1() { return of(this.response); }
  someCall2() { return of(this.response); }
  someCall3() { return of(this.response); }
  someCall4() { return of(this.response); }
  someCall5() { throw new Error('oups!') }
}

@Component({
  selector: 'app-testee',
  template: `<h1>hi</h1>`
})
export class TesteeComponent implements OnInit { // main comp
  constructor(private service: Service) {}

  result: string; // aggregated property

  ngOnInit() { }

  loadData() {
    forkJoin([
      this.service.someCall(),
      this.service.someCall1(),
      this.service.someCall2(),
      this.service.someCall3(),
      this.service.someCall4(),
      this.service.someCall5(),
    ]).subscribe(
      rse => this.result = [].concat(...rse).join('-'), // aggregation
      err => this.result = `ERR ${err}`
    );
  }
}

describe('TesteeComponent', () => {
  let fixture: ComponentFixture<TesteeComponent>;
  let component: TesteeComponent;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      declarations: [
        TesteeComponent
      ],
      providers: [
        Service
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TesteeComponent);
    component = fixture.componentInstance;
  });

  it('should load data', () => {
    const testee = component;
    // why not use TestBed.get(Service) here?
    const service = fixture.debugElement.injector.get(Service);
    spyOn(service, 'someCall').and.returnValue(of(['test', 'values', '0']));
    spyOn(service, 'someCall1').and.returnValue(of(['test', 'values', '1']));
    spyOn(service, 'someCall2').and.returnValue(of(['test', 'values', '2']));
    spyOn(service, 'someCall3').and.returnValue(of(['test', 'values', '3']));
    spyOn(service, 'someCall4').and.returnValue(of(['test', 'values', '4']));
    spyOn(service, 'someCall5').and.returnValue(of(['test', 'values', '5']));
    expect(testee).toBeTruthy();
    fixture.detectChanges();
    testee.loadData();

    expect(testee.result).toEqual('test-values-0-test-values-1-test-values-2-test-values-3-test-values-4-test-values-5');
    expect(service.someCall).toHaveBeenCalled();
  });
});

答案 1 :(得分:0)

这个问题是从头开始的,但是我希望在任何情况下它都会有所帮助。

这应该可以正常工作:

PsExec \\Acer-PC -e WScript.exe C:\MessageBox.vbs

使用如下的loadData()函数:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { TestComponent } from 'someLocation';
import { ServiceTest1 } from 'someLocation';
import { ServiceTest2 } from 'someLocation';

describe('TestComponent', () => {

    let fixture: ComponentFixture<TestComponent>;
    let component: TestComponent;
    let service1: ServiceTest1;
    let service2: ServiceTest2;
    let httpMock: HttpTestingController;

    beforeEach(() => {
      TestBed.configureTestingModule({
        declarations: [
          TestComponent
        ],
        imports: [
          HttpClientTestingModule
        ],
        providers: [
          ServiceTest1,
          ServiceTest2
        ]
      }).compileComponents();

      service1 = TestBed.inject(ServiceTest1);
      service2 = TestBed.inject(ServiceTest2);
      httpMock = TestBed.inject(HttpTestingController);

      fixture = TestBed.createComponent(TestComponent);
      component = fixture.componentInstance;

      fixture.detectChanges();

    });

    it('TestComponent should be created', () => {
      expect(component).toBeTruthy();
    });

    it('should load data', done => {

      const response0 = ['test', 'values', '0'];
      const response1 = ['test', 'values', '1'];
      const response2 = ['test', 'values', '2'];
      const response3 = ['test', 'values', '3'];
      const response4 = ['test', 'values', '4'];
      const response5 = ['test', 'values', '5'];
      const response6 = ['test', 'values', '6'];

      component.loadData().subscribe({
        next: (responses) => {

          expect(responses[0]).toEqual(response0);
          expect(responses[1]).toEqual(response1);
          expect(responses[2]).toEqual(response2);
          expect(responses[3]).toEqual(response3);
          expect(responses[4]).toEqual(response4);
          expect(responses[5]).toEqual(response5);
          expect(responses[6]).toEqual(response6);

          done();

        }
      });

      httpMock.expectOne('urlSomeCall0').flush(response0);
      httpMock.expectOne('urlSomeCall1').flush(response1);
      httpMock.expectOne('urlSomeCall2').flush(response2);
      httpMock.expectOne('urlSomeCall3').flush(response3);
      httpMock.expectOne('urlSomeCall4').flush(response4);
      httpMock.expectOne('urlSomeCall5').flush(response5);
      httpMock.expectOne('urlSomeCall6').flush(response6);

      httpMock.verify();

    });
});

还请记住,使用forkJoin时,所有可观察对象都应返回complete事件。