fakeAsync не ожидает завершения асинхронной операции

#angular #typescript #jasmine #karma-jasmine

#angular #typescript #jasmine #карма-жасмин

Вопрос:

Контекст

Я тестирую компонент, который использует службу на основе наблюдений, чтобы получить некоторые данные и показать их для целей i18n.

Служба i18n является пользовательской из-за конкретной необходимости.

Компонент работает в режиме разработки (используется в некоторых шаблонах, работает нормально), но тест завершается неудачно.

Источник

Компонент

 @Component({
    selector     : "i18n",
    template     : '<span [innerHTML]="text"></span><span #wrapper hidden="true"><ng-content></ng-content><span>',
    encapsulation: ViewEncapsulation.None
})
export class I18nComponent implements OnChanges {

    constructor(private i18n:I18n) {
    }

    @ViewChild('wrapper')
    content:ElementRef;

    @Input('key')
    key:string;

    @Input('domain')
    domain:string;

    @Input('variables')
    variables:Variables = [];

    @Input("plural")
    plural:number;

    text:string;

    ngOnChanges():any {
        this.i18n.get(this.key, this.content.nativeElement.innerHTML, this.variables, this.domain).subscribe((res) => {
            this.text = res;
        });
    }
}
  

I18n.get

 public get(key:string,
               defaultValue?:string,
               variables:Variables = {},
               domain?:string):Observable<string>{
    const catalog = {
                         "StackOverflowDomain":
                         {
                             "my-key":"my-value"
                         }
                    };

    return Observable.of(catalog[domain][key]).delay(300);
}
  

с Variables :

 export interface Variables {
    [key:string]:any;
}
  

Тест

 describe("I18n component", () => {

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers   : [
                I18n,
                {
                    provide : I18N_CONFIG,
                    useValue: {
                        defaultLocale : "fr_FR",
                        variable_start: '~',
                        variable_end  : '~'
                    }
                },
                {
                    provide : I18N_LOADERS,
                    useClass: MockLocaleLoader,
                    multi   : true
                }
            ],
            declarations: [
                I18nComponent
            ]
        });
        fixture = TestBed.createComponent<I18nComponent>(I18nComponent);
        comp = fixture.componentInstance;
    });

    fit("can call I18n.get.", fakeAsync(() => {
        comp.content.nativeElement.innerHTML = "nope";
        comp.key = "test";
        comp.domain = "test domain";
        comp.ngOnChanges();
        tick();
        fixture.detectChanges();
        expect(comp.text).toBe("test value");
    }));

});
  

Проблема

Тест завершается ошибкой с сообщением :

Ожидается, что неопределенное значение будет «тестовым значением».

Ошибка: 1 периодический таймер (ы) все еще в очереди.

Потому i18n.get что не завершил свою работу до проверки утверждения, поэтому comp.text все еще undefined .

Уже пробовал

  • Добавление очень высокого значения в tick вызов метода ничего не изменило (пробовал с 5000).
  • Сделайте ngOnChanges возврат a Promise<void> , который будет разрешен сразу после this.text = res; и измените fakeAsync зону для простого теста, используя done метод, указанный в then comp.ngOnChanges . Это работает, но ngOnChanges не должно возвращать a Promise , и я хочу чистое решение.

Комментарии:

1. Можете ли вы предоставить достаточный пример реализации get метода для воспроизведения проблемы. Я не уверен, как это реализовать, чтобы получить ошибку

2. Я отредактировал вопрос, чтобы добавить реализацию макета для i18n.get

3. Действительно ли используется ваш реальный код delay ? Причина, по которой я спрашиваю, заключается в том, что я пытаюсь переключиться с использования fakeAsync на async , чтобы посмотреть, имеет ли это какое-либо значение, но, похоже delay , использует setInterval , которое нельзя использовать внутри async .

4. мой код не использует задержку, я просто добавил ее для имитации асинхронного поведения. но из-за асинхронной природы Obervable он должен работать так же без задержек.

5. Вот в чем дело. У меня все работает нормально, просто возвращаясь Observable.of . У меня не было проблем. Вот почему я попросил пример воспроизведения проблемы :/

Ответ №1:

Обратите внимание, что async и fakeasync не такие мощные и всеобъемлющие, как jasmine.done

Принимая во внимание точное примечание (на момент написания этой статьи): https://angular.io/guide/testing#component-fixture

В нем говорится:

Написание тестовых функций с помощью done, хотя и более громоздкое, чем async и fakeAsync, является жизнеспособным и иногда необходимым методом. Например, вы не можете вызвать async или fakeAsync при тестировании кода, который включает intervalTimer, как это обычно бывает при тестировании async Observable