【问题标题】:Unit testing in Angular: Mocking RxJS observable with JasmineAngular 中的单元测试:Mocking RxJS observable with Jasmine
【发布时间】:2021-07-02 11:51:52
【问题描述】:

我正在对 Angular 12 组件进行单元测试。该组件在初始化时获取从服务返回的可观察对象(参见下面的thing.service.ts)。它被分配给一个主题,该主题通过async管道显示在html模板中(参见下面的app.component.html)。

AppComponent.ts

export class AppComponent  {
  public errorObjectSubject = null;
  public thingsSubject: Subject<Array<IThing>> = new Subject();

  constructor(private readonly _service: ThingService) {
    this._getAllProducts();
  }

  private async _getAllProducts(): Promise<void> {
    this._service.getAllObservableAsync()
      .pipe(take(1),
        catchError(err => {
          this.errorObjectSubject = err;
          return throwError(err);
        })
      ).subscribe(result => { this.thingsSubject.next(result) });
  }
}

模板使用async 管道订阅public thingsSubject: Subject&lt;Array&lt;IThing&gt;&gt; = new Subject();

app.component.html

<div>
  <app-thing *ngFor="let thing of thingsSubject | async" [thing]="thing"></app-thing>
</div>

thing.service.ts

  constructor(private readonly _http: HttpClient) { }

  public getAllObservableAsync(): Observable<Array<IThing>> {
    return this._http.get<Array<IThing>>('https://jsonplaceholder.typicode.com/todos'); }

这是测试设置。

app.component.spec.ts

describe('AppComponent', () => {
  let component: AppComponent,
    fixture: ComponentFixture<AppComponent>,
    dependencies: { thingService: ThingServiceStub };

  function getThings(): Array<DebugElement> {
    return fixture.debugElement.queryAll(By.directive(ThingComponentStub));
  }

  beforeEach(async () => {
    dependencies = {
      thingService: new ThingServiceStub()
    };
    await TestBed.configureTestingModule({
      declarations: [AppComponent, ThingComponentStub],
      providers: [
        { provide: ThingService, useValue: dependencies.thingService }
      ]
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    //fixture.detectChanges();
  });

  describe('on initialisation', () => {
    let getThingsSubject: Subject<Array<IThing>>;

    beforeEach(() => {
      getThingsSubject = new Subject();
      (dependencies.thingService
        .getAllObservableAsync as jasmine.Spy).and.returnValue(
        getThingsSubject.asObservable()
      );
      fixture.detectChanges();
    });

    it('should fetch all of the things', () => {
      //fixture.detectChanges();
      expect(
        dependencies.thingService.getAllObservableAsync
      ).toHaveBeenCalledWith();
    });

    describe('when the things have been fetched', () => {
      beforeEach(fakeAsync(() => {
        getThingsSubject.next()
        // getThingsSubject.next([
        //   {
        //     userId: 1,
        //     id: 1,
        //     title: 'string',
        //     completed: 'string'
        //   }
        // ]);
        //getThingsSubject.pipe().subscribe()

        tick();

        fixture.detectChanges();
      }));

      it('should display the things', () => {
        expect(getThings()[0].componentInstance.product).toEqual({
          name: 'product',
          number: '1'
        });
      });
    });
  });
});

thing.service.stub.ts

export class ProductServiceStub {
    public getAllObservableAsync: jasmine.Spy = jasmine.createSpy('getAllObservableAsync');
  }

我正在尝试在模板中填充了东西 (IThing[]) 后测试它是如何工作的。我有一个 passing 规范,它调用了模拟 observable:

it('should fetch all of the things', () => {
  expect(
    dependencies.thingService.getAllObservableAsync
  ).toHaveBeenCalledWith();
});

但是,当我尝试测试模板时,我遇到了“错误:未捕获(承诺):TypeError:无法读取 undefine 的属性“管道”:describe('when the things have been fetched'

我不太确定是什么问题。这是我如何设置对主题的订阅吗?还是变化检测?

【问题讨论】:

    标签: angular jasmine angular12


    【解决方案1】:

    我认为你调用事物的顺序可能是个问题。

    试试这个:

    describe('AppComponent', () => {
      let component: AppComponent,
        fixture: ComponentFixture<AppComponent>,
        dependencies: { thingService: ThingServiceStub };
      
    
      function getThings(): Array<DebugElement> {
        return fixture.debugElement.queryAll(By.directive(ThingComponentStub));
      }
    
      beforeEach(async () => {
        dependencies = {
          thingService: new ThingServiceStub()
        };
        await TestBed.configureTestingModule({
          declarations: [AppComponent, ThingComponentStub],
          providers: [
            { provide: ThingService, useValue: dependencies.thingService }
          ]
        }).compileComponents();
      });
    
      beforeEach(() => {
        // !! This (.createComponent) is when the constructor is called, so mock the observable
        // before here and change the subject to a BehaviorSubject.
        // Maybe subject will work as well.
        let getThingsSubject = new BehaviorSubject([{ name: 'product', number: '1' }]);
        (dependencies.thingService.getAllObservableAsync as jasmine.Spy).and.returnValue(
           getThingsSubject.asObservable()
        );
        fixture = TestBed.createComponent(AppComponent);
        component = fixture.componentInstance;
      });
    
      describe('on initialisation', () => {
        let getThingsSubject: Subject<Array<IThing>>;
    
        it('should fetch all of the things', () => {
          expect(
            dependencies.thingService.getAllObservableAsync
          ).toHaveBeenCalledWith();
        });
    
        describe('when the things have been fetched', () => {
          // maybe tick and fakeAsync are not needed but `fixture.detectChanges` is
          beforeEach(fakeAsync(() => {
    
            tick();
    
            fixture.detectChanges();
          }));
    
          it('should display the things', () => {
            // !! Add this log for debugging
            console.log(fixture.nativeElement);
            expect(getThings()[0].componentInstance.product).toEqual({
              name: 'product',
              number: '1'
            });
          });
        });
      });
    });
    

    【讨论】:

    • 谢谢。我在it('should display the products' 规范上收到“TypeError: Cannot read property 'componentInstance' of undefined”。尝试使用和不使用 fakeAsync / Tick()。有什么想法吗?
    • 我为第二次测试添加了一个 console.log,它应该可以帮助您调试。我在想ThingComponentStubs 没有画在视图中。也许*ngIf 阻止了他们。
    • 非常感谢。 console.log 帮助了我很多(我不知道可以登录单元测试)。这是我的愚蠢错误。我在外部“描述”之前关闭了内部describe。换句话说,我删除了});,它就在describe('when the things have been fetched', () =&gt; { 之前。非常感谢您的帮助。 :)
    猜你喜欢
    • 2018-07-01
    • 2017-08-17
    • 1970-01-01
    • 2017-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-23
    • 2019-05-01
    相关资源
    最近更新 更多