2

Jasmine で MyDirective という名前の構造ディレクティブをテストしようとしています。使用される Angular のバージョンは RC5 です。

// Part of the MyDirective class

@Directive({selector: '[myDirective]'})
export class MyDirective {
    constructor(protected templateRef: TemplateRef<any>,
                protected viewContainer: ViewContainerRef,
                protected myService: MyService) {
    }

    ngOnInit() {
        this.myService.getData()
            .then((data) => {
                if (!MyService.isValid(data)) {
                    this.viewContainer.createEmbeddedView(this.templateRef);
                } else {
                    this.viewContainer.clear();
                }
            })
            .catch((error) => {
                console.log(error);
                this.viewContainer.createEmbeddedView(this.templateRef);
            });
    }
}

getData メソッドは MockService クラスで上書きされますが、データの有効性をチェックする isValid メソッド (MyService の静的メソッド) は直接呼び出されます。

// Part of the Jasmine unit test class for the MyDirective class

@Component({
    selector: 'test-cmp', template: '', directives: [MyDirective]
})
class TestComponent {}

class MockService {
    mockResponse: MyResponse = {valid date goes here};
    mockInvalidResponse: MyResponse = {};

    getData() {
        if (booleanCondition) {
            return Promise.resolve(this.mockResponse);
        } else {
            return Promise.resolve(this.mockInvalidResponse);
        }
    }
}

describe('MyDirective', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent],
            providers: [
                {provide: MyService, useClass: MockService},
                TemplateRef,
                ViewContainerRef
            ]
        });
    });

    it('should remove the target DOM element when the condition is true', async(() => {
        booleanCondition = true;
        const template =
             '<div><div *myDirective><span>Hi</span></div></div>';

        TestBed.overrideComponent(TestComponent, {set: {template: template}});
        let fixture = TestBed.createComponent(TestComponent);
        fixture.detectChanges();
        expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length).toEqual(0);
    }));

    it('should contain the target DOM element when the condition is false', async(() => {
        booleanCondition = false;
        const template =
             '<div><div *myDirective><span>Hi</span></div></div>';

        TestBed.overrideComponent(TestComponent, {set: {template: template}});
        let fixture = TestBed.createComponent(TestComponent);
        fixture.detectChanges();

        // The 'expect' bellow fails because the value is 0 for some reason
        expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length).toEqual(1);
    }));
});

2 つ目itは、span 要素が DOM にあるケースを作成することになっていますが、そうではありません。次のような if ステートメントの最初の条件に当てはまるかどうかを確認しました。

if (!MyService.isValid(data)) {
        console.log('the first if condition is read.');
        this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
        this.viewContainer.clear();
    }
}

そして、ログに記録します。したがって、要素をDOMに保持する必要がありますが、テストする方法が見つかりません。

4

1 に答える 1

4

a Promise(から返されたものgetData) が非同期であるためです。したがって、すべての同期アクティビティは、Promiseアクティビティより先に処理されます。ngOnInitが呼び出されても、Promise非同期で解決されます。

このタイプのものに通常使用するオプションがいくつかあります。

1 つのオプションは、fakeAsyncの代わりに使用することですasynctickこれにより、非同期アクションを同期的に完了するために呼び出すことができます

import { fakeAsync, tick } from '@angular/core/testing';

it('... when the condition is false', fakeAsync(() => {
  const template = '<div><div *myDirective><span>Hi</span></div></div>';

  TestBed.overrideComponent(TestComponent, { set: { template: template } });
  let fixture = TestBed.createComponent(TestComponent);
  fixture.detectChanges();
  // tick can also be called with a millisecond delay argument `tick(1000)`
  tick();          
  expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length)
    .toEqual(1);
}));

もう 1 つのオプションは、模擬サービスを同期させることです。getData()これは、サービス自体を返す呼び出しを行い、 thenandcatchメソッドをサービスに追加することで簡単に実行できます。例えば

class MockMyService {
  data;
  error;

  getData() {
    return this;
  }

  then(callback) {
    if (!this.error) {
      callback('mockData');
    }
    return this;
  }

  catch(callback) {
    if (this.error) {
      callback(this.error);
    }
  }

  setData(data) {
    this.data = data;
  }

  setError(error) {
    this.error = error;
  }
}

このアプローチの利点の 1 つは、テストの実行中にサービスをより詳細に制御できることです。これは、 を使用するコンポーネントをテストする場合にも非常に役立ちますtemplateUrlで XHR 呼び出しを行うことはできないfakeAsyncため、それを使用することはできません。ここで、同期モック サービスが使用されます。

サービスをテストケースに挿入するかit、変数をテストに保持して次のように設定することができます

let mockMyService: MockMyService;

beforeEach(() => {
  mockMyService = new MockMyService();
  TestBed.configureTestingModule({
    providers: [
      { provide: MyService, useValue: mockMyService }
    ]
  });
});

注:上記の理由により、現在のテストは有効ではないため、合格したテストも修正する必要があります。


関連項目:

于 2016-09-11T14:39:14.017 に答える