使用FakeAsync對Angular非同步程式碼進行單元測試

汪子熙發表於2020-12-20

The problem with async is that we still have to introduce real waiting in our tests, and this can make our tests very slow.

Async的一個缺陷是,我們仍然需要在測試程式碼中引入真實的等待,這會拖慢我們的單元測試速度。

fakeAsync comes to the rescue and helps to test asynchronous code in a synchronous way.

FakeAsync可以允許我們以同步的方式去測試非同步程式碼。

一個例子:

<h1>
  {{ incrementDecrement.value }}
</h1>

<button (click)="increment()" class="increment">
  Increment
</button>

increment方法的實現:

increment() {
  this.incrementDecrement.increment();
}

incrementDecrement service的實現:

increment() {
  setTimeout(() => {
    if (this.value < 15) {
      this.value += 1;
      this.message = '';
    } else {
      this.message = 'Maximum reached!';
    }
  }, 5000); // wait 5 seconds to increment the value
}

測試程式碼:

import { TestBed, fakeAsync, tick, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { AppComponent } from './app.component';
import { IncrementDecrementService } from './increment-decrement.service';
describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let debugElement: DebugElement;
  beforeEach(
    async(() => {
      TestBed.configureTestingModule({
        declarations: [AppComponent],
        providers: [IncrementDecrementService]
      }).compileComponents();
  fixture = TestBed.createComponent(AppComponent);
  debugElement = fixture.debugElement;
})  );
  it('should increment in template after 5 seconds', fakeAsync(() => {
      debugElement
        .query(By.css('button.increment'))
        .triggerEventHandler('click', null);
  tick(2000);
  fixture.detectChanges();
  let value = debugElement.query(By.css('h1')).nativeElement.innerText;
  expect(value).toEqual('0'); // value should still be 0 after 2 seconds

  tick(3000);
  fixture.detectChanges();

  const value = debugElement.query(By.css('h1')).nativeElement.innerText;
  expect(value).toEqual('1'); // 3 seconds later, our value should now be 1
}));

FakeAsync block內的tick程式碼,能在特定的zone裡,模擬時間的流逝。Tick引數的單位是毫秒。

Tick can also be used with no argument, in which case it waits until all the microtasks are done (when promises are resolved for example).

如果不帶引數,則直至所有的microtasks都結束,即promises都resolved之後再返回。

也可以選用flush方法:It basically simulates the passage of time until the macrotask queue is empty. Macrotasks include things like setTimouts, setIntervals and requestAnimationFrame.

直至所有的microtasks都結束,包括setTimeouts,setIntervals和requestAnimationFrame結束,flush才返回。

使用flush改寫的例子:

it('should increment in template', fakeAsync(() => {
  debugElement
    .query(By.css('button.increment'))
    .triggerEventHandler('click', null);

  flush();
  fixture.detectChanges();

  const value = debugElement.query(By.css('h1')).nativeElement.innerText;
  expect(value).toEqual('1');
}));

相關文章