• [Unit Testing Angular] RxJS Marble testing for Component


    Component:

    import { Component, OnInit } from "@angular/core";
    import { TwainService } from "../twain.service";
    import { Observable, of } from "rxjs";
    import { startWith, catchError } from "rxjs/operators";
    
    @Component({
      selector: "twain",
      templateUrl: "./twain.component.html",
      styleUrls: ["./twain.component.scss"]
    })
    export class TwainComponent implements OnInit {
      errorMessage: string;
      quote: Observable<any>;
      constructor(private twainService: TwainService) {}
    
      ngOnInit() {
        this.getQuote();
      }
      getQuote() {
        this.errorMessage = "";
        this.quote = this.twainService.getQuote().pipe(
          startWith("..."),
          catchError((err: any) => {
            // Wait a turn because errorMessage already set once this turn
            setTimeout(() => (this.errorMessage = err.message || err.toString()));
            return of("..."); // reset message to placeholder
          })
        );
      }
    }
    <p class="twain">
      <i>{{ quote | async }}</i>
    </p>
    <button (click)="getQuote()">Next quote</button>
    <p class="error" *ngIf="errorMessage">{{ errorMessage }}</p>

    Testing code:

    import {
      async,
      ComponentFixture,
      TestBed,
      fakeAsync,
      tick
    } from "@angular/core/testing";
    import { of, throwError } from "rxjs";
    import { TwainComponent } from "./twain.component";
    import { TwainService } from "../twain.service";
    import { cold, getTestScheduler } from "jasmine-marbles";
    
    fdescribe("TwainComponent", () => {
      let component: TwainComponent;
      let fixture: ComponentFixture<TwainComponent>;
      let quoteEl;
      let testQuote;
      let getQuoteSpy;
    
      beforeEach(() => {
        testQuote = "Test Quote";
    
        // Create a fake TwainService object with a `getQuote()` spy
        const twainService = jasmine.createSpyObj("TwainService", ["getQuote"]);
        // Make the spy return a synchronous Observable with the test data
        getQuoteSpy = twainService.getQuote.and.returnValue(of(testQuote));
    
        TestBed.configureTestingModule({
          declarations: [TwainComponent],
          providers: [{ provide: TwainService, useValue: twainService }]
        });
        fixture = TestBed.createComponent(TwainComponent);
        component = fixture.componentInstance;
        quoteEl = fixture.nativeElement.querySelector(".twain");
      });
    
      it("should show quote after getQuote (marbles)", () => {
        // observable test quote value and complete(), after delay
        const q$ = cold("-a|", { a: testQuote });
        getQuoteSpy.and.returnValue(q$);
    
        fixture.detectChanges(); // ngOnInit()
        expect(quoteEl.textContent).toBe("...", "should show placeholder");
    
        getTestScheduler().flush(); // flush the observables
    
        fixture.detectChanges(); // update view
    
        expect(quoteEl.textContent).toBe(testQuote, "should show quote");
        expect(component.errorMessage).toBe("", "should not show error");
      });
    
      it("should display error when TwainService fails", fakeAsync(() => {
        // observable error after delay
        const q$ = cold("---#|", null, new Error("TwainService test failure"));
        getQuoteSpy.and.returnValue(q$);
    
        fixture.detectChanges(); // ngOnInit()
        expect(quoteEl.textContent).toBe("...", "should show placeholder");
    
        getTestScheduler().flush(); // flush the observables
        tick(); // component shows error after a setTimeout()
        fixture.detectChanges(); // update error message
    
        expect(component.errorMessage).toMatch(
          /test failure/,
          "should display error"
        );
        expect(quoteEl.textContent).toBe("...", "should show placeholder");
      }));
    });

    The beauty of marble testing is in the visual definition of the observable streams. This test defines a cold observable that waits three frames (---), emits a value (x), and completes (|). In the second argument you map the value marker (x) to the emitted value (testQuote).

    const q$ = cold("-a|", { a: testQuote });

    For error case:

    const q$ = cold('---#|', null, new Error('TwainService test failure'));

    This is a cold observable that waits three frames and then emits an error, The hash (#) indicates the timing of the error that is specified in the third argument. The second argument is null because the observable never emits a value.

  • 相关阅读:
    AcWing 240. 食物链
    AcWing 886. 求组合数 II
    AcWing 734. 能量石
    扩展CRT(扩展中国剩余定理)
    AcWing 12. 背包问题求具体方案
    AcWing 487. 金明的预算方案
    Lucas(卢卡斯)定理
    Neovim下Tutor的常用命令总结
    AcWing 10. 有依赖的背包问题
    AcWing 11. 背包问题求方案数
  • 原文地址:https://www.cnblogs.com/Answer1215/p/12403675.html
Copyright © 2020-2023  润新知