Skip to content

Instantly share code, notes, and snippets.

@aliyome
Last active March 18, 2020 05:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save aliyome/4cb1fd5c92c2745083f8f82e89a58b79 to your computer and use it in GitHub Desktop.
Save aliyome/4cb1fd5c92c2745083f8f82e89a58b79 to your computer and use it in GitHub Desktop.
Angularのテストについて まとめ

https://qiita.com/Quramy/items/83f4fbc6755309f78ad2

Componentのテストで頻出するパターンをまとめると、次のようになります。

  • MicroTask(Promise.then)を実行させたい: fixture.whenStable, fakeAsync + flushMicrotasks, fakeAsync + tick
  • MacroTaskの完了を待ちたい: fixture.whenStable
  • MacroTask(但しタイマー系のみ)を実行したい: fakeAsync + tick
  • EventTask(但しDOMのみ)を強制的に実行したい: fixture.query(...).triggerEventHandler

各taskの終了を待った後、fixture.detectChanges を実行すれば、テスト対象のComponentとそのViewを狙った状態にすることが出来ると思います。 AppricationRefの挙動である各taskの実行後で、且つMicroTaskが空の場合に変更検知を行うを押えておけばどうということはありません6。

当然と言えば当然なのですが、XHRのようにブラウザの外部と通信するようなケースはどうしようもないので、serviceをmockingするなり、HttpBackendを使うなり、JasmineのspyOnを使うなり工夫しましょう。

まとめ

  • Zone.jsは非同期処理をinterceptする機能を持ったユーティリティ
  • MicroTask(Promise), MacroTask(timer系), EventTask(DOM等)の3種類
  • AngularはZoneのtask状態を監視して変更検知を実行している
  • テストコードではfixture.whenStable や tick, flushMicrotasks, triggerEventHandlerでtask実行を制御し、その後 fixture.detectChangesを実行するようにする

Jasmine Observable Test

it('should be done after subscribe', (done: DoneFn) => {
    service.getSomeObservable().subscribe(x => {
        expect(x).toBe('expected');
        done();
    });
});

Jasmine Mocking Pattern

class Service {
    constructor(private valueService) { }
    getValue() { return this.valueService.getValue(); }
}
it('should return mocked value.', () => {
    service = new Service(new ValueService());
    expect(service.getValue()).toBe('real value');
    service = new Service(new FakeValueService());
    expect(service.getValue()).toBe('fake value');
    service = new Service({ getValue: () => 'fake obj value' });
    expect(service.getValue()).toBe('fake obj value');
    const spy = jasmine.createSpyObj('ValueService', ['getValue']);
    spy.getValue.and.returnValue('stub value');
    service = new Service(spy);
    expect(service.getValue()).toBe('stub value');
    expect(spy.getValue.calls.count).toBe(1);
    expect(spy.getValue.calls.mostRecent().returnValue).toBe('stub value');
});

TestBed tips

providers: [
    Service,
    { provide: ValueService, useValue: spy }
]

service = TestBed.get(Service);  // injected spy object

Helper

asyncData(value);  // return Promised Value (defer(() => Promise.resolve(value)))
asyncError(value);  // return Promised Error ....reject(value)

Http

// if you use HttpClientTestingModule, you don't do below.
httpClientSpy.get.and.returnValue(asyncError(errorResponse));
service.request().subscribe(_ => fail('unreachable'), err => {
    expect(err).toBe(errorResponse);
})

dispatchEvent(new Event()) in IE11

if(typeof(Event) === 'function') {
    var event = new Event('submit');
}else{
    var event = document.createEvent('Event');
    event.initEvent('submit', true, true);
}

$el.dispatchEvent(event);

// other method
heroDe.triggerEventHandler('click', null);

/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
export const ButtonClickEvents = {
   left:  { button: 0 },
   right: { button: 2 }
};

/** Simulate element click. Defaults to mouse left-button click event. */
export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void {
  if (el instanceof HTMLElement) {
    el.click();
  } else {
    el.triggerEventHandler('click', eventObj);
  }
}
click(heroDe)

ComponentTest

// You MUST call this if your copmonent has templateUrl, stylesUrl.
TestBed.compileComponents();

// get Text
fiture.nativeElement.querySelector('.hoge').textContent

// Async test **except** for XHR
it('hoge', fakeAsync(() => {
    fixture.detectChanges();  // until ngOnInit()
    tick();  // wait for setTimeout or next observable
    fixture.detectChanges();  // after setTimeout
}));
// 
it('foo', async(() => {
  fixture.whenStable().then(() => { // wait for async getQuote
    fixture.detectChanges();        // update view with quote
    expect(quoteEl.textContent).toBe(testQuote);
    expect(errorMessage()).toBeNull('should not show error');
  });
}));

Advanced Typescript tips

// Partial
interface User { a:any, b:any, c:any }
let user: Partial<User> = { c:'hoge' };
// Partial<User> -> User { a?:any, b?:any, c?:any }

Matcher

toMatch(/regex/, 'target');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment