tekitoumemo’s diary

思ったことを書くだけ。長文版Twitter

【Jasmine、TypeScript】JasmineのSpyOnでsubscribe内のメソッドが検知出来ない

https://cdn-ak.f.st-hatena.com/images/fotolife/o/ochimusha01/20170827/20170827055211.jpg

最近、SPAが流行っていて私もAngular5に四苦八苦しています。Angularの(っていうかJSの)テストフレームワークの「Jasmine」でテストしながら開発を進めているのですが、早速詰んだので解決方法を書きます。

「JasmineのSpyOnでsubscribe内のメソッドが検知出来ない」とはどういう状況?

以下のコードからngOnInitでroute.eventsのsubscribeで呼び出しているscrollを検知したいので、テスト側でSpyOnして実行します。が、これが失敗します。alertで呼び出されていることがわかるので実際には動いているはずです。

コード側
public ngOnInit() {
    this.route.events.subscribe(event => {
        if (event instanceof NavigationEnd) {
            window.scroll(100, 100);
            alert("ここ呼ばれているはずだよ!!");
        }
    })
}
テスト側
import { assert } from 'chai';
import { CounterComponent } from './counter.component';
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { RouterModule , Router, NavigationEnd ,Routes } from "@angular/router";
import {APP_BASE_HREF} from '@angular/common';
let fixture: ComponentFixture<CounterComponent>;

const appRoutes: Routes = [
    {path: 'counter', component: CounterComponent}
  ];

describe('Counter component', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [CounterComponent],
            imports: [
                RouterModule.forRoot(appRoutes)
              ],
              providers: [{provide: APP_BASE_HREF, useValue : '/' }]
        })
        fixture = TestBed.createComponent(CounterComponent);
        fixture.detectChanges();
    });

    it('test', async(() => {
        let router: Router;
        router = TestBed.get(Router); 
        spyOn(window, "scroll");
        router.navigate(["/counter"]);
        fixture.componentInstance.ngOnInit();
        expect(window.scroll).toHaveBeenCalled();

    }));
});

実行すると呼び出されていないよというエラーが出ます。

Expected spy scroll to have been called

これは単純にexpectが呼び出されるときにsubscribeが動いていないってことでした。subscribeは実行後の後続処理的なやつで非同期で動きます。なので、expectしたときにまだ動いてなくて検知出来なかっただけというオチでした。

以下が変更点です。

it('test', fakeAsync(() => {
    let router: Router;
    router = TestBed.get(Router); 
    spyOn(window, "scroll");
    router.navigate(["/counter"]);
    fixture.componentInstance.ngOnInit();
    tick(); // or flushMicrotasks()
    expect(window.scroll).toHaveBeenCalled();

}));

fakeAsyncで定義してtick()で処理を待ちます。flushMicrotasksでも良いですが、tickで待ってから評価した方が良い気がします。

むずいわ。