[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.

猜你喜欢

转载自www.cnblogs.com/Answer1215/p/12403675.html