微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Angular 集成测试 - jasmine Spy - 无法模拟返回 Observable 的服务方法

如何解决Angular 集成测试 - jasmine Spy - 无法模拟返回 Observable 的服务方法

我是做集成测试的新手。整个事情是如此令人困惑。

对于我的第一次测试,似乎我的间谍没有像我打算返回的那样返回数据。给予和错误:预计 0 为 3。如果有人能帮助我理解我做错了什么,那就太好了。

这是我的服务、页面、规范文件和模板:

我的服务


    import { Data } from './../data/data.model';
    import { Injectable } from '@angular/core';
    import { BehaviorSubject,of } from 'rxjs';
    import { tap } from 'rxjs/operators';

    @Injectable({
      providedIn: 'root',})
    export class MyService {
      private _data = new BehaviorSubject<Data[]>([]);

      get data() {
        return this._data;
      }

      constructor() {}

      getAllData() {
        return of([
          {
            id: '1',title: 'Rice',},{
            id: '2',title: 'Wheat',{
            id: '33',title: 'Water',]).pipe(
          tap((data) => {
            this._data.next(data);
          })
        );
      }
    }

数据页面组件


    import { Component,OnInit } from '@angular/core';
    import { BehaviorSubject,Observable,of,Subscription } from 'rxjs';
    import { MyService } from '../services/my.service';
    import { Data } from './data.model';

    @Component({
      selector: 'app-data',templateUrl: './data.page.html',styleUrls: ['./data.page.scss'],})
    export class DataPage implements OnInit {
      allData: Data[];
      dataServiceSub: Subscription;
      isLoading: boolean;

      constructor(private myService: MyService) {}

      ngOnInit() {
        this.dataServiceSub = this.myService.data.subscribe(
          (data) => {
            console.log(data);
            this.allData = data;
          }
        );
      }

      ngOnDestroy() {
        if (this.dataServiceSub) {
          console.log('ngOnDestroy');
          this.dataServiceSub.unsubscribe();
        }
      }

      ionViewWillEnter() {
        this.isLoading = true;
        this.myService.getAllData().subscribe(() => {
          console.log('ionViewWillEnter');
          this.isLoading = false;
        });
      }
    }

DataPage.spec

    import { MyService } from '../services/my.service';
    import { async,ComponentFixture,Testbed } from '@angular/core/testing';
    import { IonicModule } from '@ionic/angular';

    import { DataPage } from './data.page';
    import { of } from 'rxjs';

    describe('DataPage',() => {
      let component: DataPage;
      let fixture: ComponentFixture<DataPage>;
      let serviceSpy: jasmine.SpyObj<MyService>;

      beforeEach(async(() => {
        Testbed.configureTestingModule({
          declarations: [DataPage],providers: [
            {
              provide: MyService,useClass: MyService
            },],imports: [IonicModule.forRoot()],}).compileComponents();

        fixture = Testbed.createComponent(DataPage);
        component = fixture.componentInstance;
        fixture.detectChanges();
      }));

      fit('Should show list of data if data is available',() => {
        serviceSpy = Testbed.get(MyService);
        spyOn(serviceSpy,'getAllData').and.returnValue(of([
          {
            id: '1',]));
        fixture.detectChanges();
        const element = fixture.nativeElement.querySelectorAll(
          '[test-tag="dataList"] ion-item'
        );
        console.log(
          fixture.nativeElement.querySelectorAll('[test-tag="dataList"]')
        );
        expect(element.length).toBe(3);
      });
    });

HTML


    <ion-content>
      <div test-tag="empty" class="ion-text-center">
        <ion-text color="danger">
          <h1>No data</h1>
        </ion-text>
      </div>
      <div test-tag="dataList">
        <ion-list>
          <ion-item *ngFor="let data of allData">
            <ion-label test-tag="title">{{data.title}}</ion-label>
          </ion-item>
        </ion-list>
      </div>
    </ion-content>


解决方法

好的,问题来了:

您需要调用ionViewWillEnter()来设置this.allData'

的值

原因:因为您在创建 BehaviorSubject 时有空值。要使用 data (this._data.next(data)) 发出值,您需要调用 getAllData()

 import { MyService } from '../services/my.service';
    import { async,ComponentFixture,TestBed } from '@angular/core/testing';
    import { IonicModule } from '@ionic/angular';

    import { DataPage } from './data.page';
    import { of } from 'rxjs';

    describe('DataPage',() => {
      let component: DataPage;
      let fixture: ComponentFixture<DataPage>;
      let serviceSpy: jasmine.SpyObj<MyService>;

      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [DataPage],providers: [ MyService ],imports: [IonicModule.forRoot()],}).compileComponents();

        fixture = TestBed.createComponent(DataPage);
        component = fixture.componentInstance;
        fixture.detectChanges();
      }));

      fit('Should show list of data if data is available',() => {
        component.ionViewWillEnter(); // or create an event which will trigger ionViewWillEnter()
        fixture.detectChanges();
        const element = fixture.nativeElement.querySelectorAll(
          '[test-tag="dataList"] ion-item'
        );
        console.log(
          fixture.nativeElement.querySelectorAll('[test-tag="dataList"]')
        );
        expect(element.length).toBe(3);
      });
    });

请注意,我所做的更改很少:

  1. 删除了 UseClass(因为您没有按应有的方式使用它)
  2. 删除了 spy(因为您在原始服务中已经有了硬编码值)

为了更好地理解 angular 测试,您可以参考 my article,其中也展示了 useClass 的用法供您参考。


附带说明:尝试使用 asObservable(),并在创建 $ (Observable) 时遵循使用 this.data$.asObservable() 的约定。这不是强制性的,而是 JS 社区公认的做法。

get data() {
  return this._data.asObservable();
}
,

为了避免 observables 的痛苦,我建议使用像 ng-mocks 这样的模拟库,并查看它的文章“如何在 Angular 测试中模拟 observable 流”https://ng-mocks.sudo.eu/extra/mock-observables

在您的情况下,测试可能如下所示:

describe('DataPage',() => {
  // mocks everything except DataPage
  beforeEach(() => {
    return MockBuilder(DataPage)
      .mock(IonicModule.forRoot())
      .mock(MyService);
  });

  // We need to stub it because of subscription in ionViewWillEnter.
  // in a mock service,ionViewWillEnter does not return anything.
  // But we need to tell it to return an empty observable stream
  // to avoid errors like cannot call .subscribe on undefined.
  // This line can be removed along with the debugging from the
  // component.
  beforeEach(() => MockInstance(MyService,'getAllData',() => EMPTY));

  it('Should show list of data if data is available',() => {
    // spies the getter of the property the component uses.
    MockInstance(MyService,'data',jasmine.createSpy(),'get')
      .and.returnValue(of([
        {
          id: '1',title: 'Rice',},{
          id: '2',title: 'Wheat',{
          id: '33',title: 'Water',]));

    // render (already with detected changes)
    const fixture = MockRender(DataPage);

    // assertions
    const element = fixture.nativeElement.querySelectorAll(
      '[test-tag="dataList"] ion-item'
    );
    console.log(
      fixture.nativeElement.querySelectorAll('[test-tag="dataList"]')
    );
    expect(element.length).toBe(3);
  });
});

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。