Угловое тестирование — ngBootstraps typeahead

В настоящее время я использую механизм автозаполнения (упреждающий ввод) ngBootstrap. Теперь я хочу выполнить модульное тестирование, вызывается ли метод для каждой последовательности входного события. Ошибка в моем тестовом примере в настоящее время: Cannot read property 'pipe' of undefined

HTML:

<input id="locationEdit" type="text" class="form-control"
         [(ngModel)]="user.location" name="location [ngbTypeahead]="search"/>

Составная часть:

public ngOnInit() {
    this.search = (text$: Observable<string>) =>
      text$.pipe(
        tap(() => {
          this.isSearching = true;
          this.searchFailed = false;
        }),
        debounceTime(750),
        distinctUntilChanged(),
        switchMap(term =>
          this.cityService.getLocation(term).pipe(
            tap((response) => {
              this.searchFailed = response.length === 0;
              this.isSearching = false;
            })))
      );
  }

спец.ц

  it('should call spy on city search', fakeAsync(() => {
    component.user = <User>{uid: 'test', username: 'mleko', location: null, description: null};
    const spy = (<jasmine.Spy>cityStub.getLocation).and.returnValue(of['München Bayern']);

    fixture.detectChanges();
    const compiled: DebugElement = fixture.debugElement.query(By.css('#locationEdit'));
    compiled.nativeElement.value = 'München';
    compiled.nativeElement.dispatchEvent(new Event('input'));

    tick(1000);
    fixture.detectChanges();

    expect(spy).toHaveBeenCalled();
  }));

Может ли кто-нибудь помочь мне правильно издеваться над this.search?

Редактировать

По замечательному предложению @dmcgrandle мне не нужно отображать HTML и имитировать событие ввода, чтобы проверить, работает ли ввод текста. Я скорее должен сделать Observable, который испускает значения и присваивает их функции. Один из подходов:

  it('should call spy on city search', fakeAsync(() => {
    const spy = (<jasmine.Spy>cityStub.getLocation).and.returnValue(of['München Bayern']);

    component.ngOnInit();
    const textMock = of(['M', 'Mün', 'München']).pipe(flatMap(index => index));

    component.search(textMock);

    tick();

    expect(spy).toHaveBeenCalled();
  }));

Но проблема еще в том, что component.search не вызывает шпиона. В функции поиска в операторе switchMap я добавил console.log, чтобы увидеть, выдается ли значение из функции. Но это не тот случай.


person MarcoLe    schedule 21.10.2018    source источник
comment
Ошибка исходит от text$.pipe( поставьте точку отладки или console.log и проверьте какое там значение   -  person Ntwobike    schedule 21.10.2018
comment
Попробуйте добавить spyOn(component, 'search').and.returnValue(of('some string'));   -  person HDJEMAI    schedule 21.10.2018
comment
@HDJEMAI Я уже пробовал это, но мне выдает, что метод поиска не существует для этого компонента ..   -  person MarcoLe    schedule 21.10.2018


Ответы (2)


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

Поэтому я бы предложил издеваться над пользователем, который фактически печатает, настроив собственный Observable с синхронизацией и вызвав с ним свою функцию. Возможно, имитировать отправку символа каждые 100 мс. Что-то вроде этого:

it('should call spy on city search', fakeAsync(() => {
    component.user = <User>{uid: 'test', username: 'mleko', location: null, description: null};
    // Change next line depending on implementation of cityStub ...
    const spy = spyOn(cityStub, 'getLocation').and.returnValue(of('München Bayern'));

    fixture.detectChanges();
    let inputTextArray = ['M', 'Mü', 'Mün', 'Münc', 'Münch', 'Münche', 'München'];
    let textMock$ : Observable<string> = interval(100).pipe(take(7),map(index => inputTextArray[index]));
    component.search(textMock$);
    tick(1000);
    expect(spy).toHaveBeenCalled();
}));

Обновление:

Я собрал stackblitz здесь, чтобы проверить это: https://stackblitz.com/edit/stackoverflow-question-52914753 (откройте папку приложения слева и щелкните my.component.spec.ts, чтобы увидеть тестовый файл)

Как только я получил его туда, стало очевидно, что не так — на наблюдаемое не подписывались, потому что подписка, кажется, выполняется ngBootstrap, поэтому для тестирования нам нужно подписаться явно. Вот моя новая рекомендуемая спецификация (взято из stackblitz):

it('should call spy on city search', fakeAsync(() => {
    const cityStub = TestBed.get(CityService);
    const spy = spyOn(cityStub, 'getLocation').and.returnValue(of('München Bayern'));

    fixture.detectChanges();
    let inputTextArray = ['M', 'Mü', 'Mün', 'Münc', 'Münch', 'Münche', 'München'];
    let textMock$ : Observable<string> = interval(100).pipe(take(7),map(index => inputTextArray[index]));
    component.search(textMock$).subscribe(result => {
         expect(result).toEqual('München Bayern');
    });
    tick(1000);
    expect(spy).toHaveBeenCalled();
}));
person dmcgrandle    schedule 25.10.2018
comment
Спасибо за обновление - я обновил свой ответ на основе ваших отзывов. - person dmcgrandle; 26.10.2018
comment
Спасибо за ответ и поздравляю с наградой. ^^ - person MarcoLe; 28.10.2018

Пожалуйста, попробуйте переместить наблюдаемое внутри сервиса:

Составная часть:

this.cityService.text$.pipe

Обслуживание:

export class CityService {
private _subject = null;
text$ = null;

constructor(private _httpClient: HttpClient) {
    this.init();
}

init() {
    this._subject = new BehaviorSubject<any>({});
    this.text$ = this._subject.asObservable();
}

Я могу расширить свой ответ, если вам нужна дополнительная информация.

person mruanova    schedule 26.10.2018
comment
Теперь я в замешательстве. ^^ Является ли это рекомендацией по шаблонам проектирования, потому что такая функция должна лучше вписываться в службу, а не в компонент? Или это подход к решению моей ошибки модульного тестирования? Интересуют подробности... :) - person MarcoLe; 28.10.2018