2

Я пытаюсь проверить структурную директиву MyDirective с Jasmine. Используемая угловая версия - RC5.Угловые 2 RC5 Тестирование обещаний в ngOnInit Не работает

// 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); 
    })); 
}); 

Второй it предполагается создать случай, в котором элемент Спан находится в DOM, но это не так. Я проверил, будет ли он перейти к первому условию в следующих операциях if:

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

И он регистрирует его. Таким образом, он должен содержать элемент в DOM, но я не могу найти способ его протестировать.

ответ

3

Это потому, что a Promise (один вернулся из getData) является асинхронным. Таким образом, вся синхронная активность обрабатывается перед операцией Promise. Несмотря на то, что вызывается ngOnInit, решение Promise разрешается асинхронно.

Есть пара вариантов, которые я обычно использую для этого типа.

Один из вариантов заключается в использовании fakeAsync вместо async. Это позволяет вызывать tick, чтобы обеспечить асинхронные действия для завершения синхронно

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); 
})); 

Другой варианты, чтобы сделать высмеивали синхронные службы. Вы можете легко сделать это, обратившись к getData(), чтобы вернуть сервис, и добавьте в службу then и catch. Например,

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; 
    } 
} 

Одним из преимуществ этого подхода является то, что он дает вам больший контроль над службой во время выполнения теста. Это также очень полезно при тестировании компонентов, которые используют templateUrl. XHR calls can't be made in a fakeAsync, поэтому использовать это не вариант. Здесь используется синхронный макет службы.

Вы можете либо вводить услугу своего it тестов или вы можете просто сохранить переменную протестировать и настроить его что-то вроде

let mockMyService: MockMyService; 

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

Примечания: Вы также хотите исправить ваш , пропустив тест, так как ваш текущий тест недействителен по указанным выше причинам.


Смотрите также:

  • Мой пост в Testing promise in Angular2 ngOnInit на примере насмешливый ActivatedRoute работать синхронно при тестировании компонента.
+0

Thanks @peeskillet! Я использовал первое из ваших двух решений, и он работал, как ожидалось.Вам не известно разницу между использованием «done()» Jasmine (передача проделанного параметра) и использованием [async] (https://angular.io/docs/ts/latest/api/core/testing/index/async -function.html)? [Исходный код Углового] ​​(https://angular.io/docs/ts/latest/api/core/testing/index/async-function.html) использует 'async()' вместо 'done()'. Я пытаюсь понять, как 'async()' распознает все асинхронные вызовы. Это может стоить еще одного вопроса ... – jayscript

+0

Я почти уверен, что это связано с зонами. Я все еще изучаю зоны, но я из того, что я сейчас понимаю, зонирует асинхронные методы polyfill, такие как 'setTimout' и т. Д., До такой степени, что зона знает обо всех асинхронных вызовах, сделанных в этой зоне. Зона ведет запись всех этих ожидающих задач. Проверьте [this out] (https://github.com/angular/zone.js/) –

+0

Если вы посмотрите на [источник для 'async'] (https://github.com/angular/angular/blob/master /modules/%40angular/core/testing/async.ts), вы видите, что происходит много событий в зоне. Вы также увидите, что 'async' на самом деле вызывает' done', когда в среде жасмина –