5

Before starting this question. I'm aware, that there are a lot of similar asked questions liked mine. But no solution was able to help me out.

I created a custom auto-complete with rxjs and want to test if a method gets called on a input event. But the error says the method never got called, like:

 Expected spy CityService.getLocation to have been called with [ 'mun' ] but it was never called.

html

I subscribe to my observable in the HTML via the async pipe.

      <input type="text" [(ngModel)]="location" class="form-control" id="locationSearchInput"/>
      <div class="spacer">
        <p class="invalid-feedBack" *ngIf="searchFailed && location.length > 0">Nothing found.</p>
        <ul id="search" *ngFor="let item of (search | async)">
          <li class="resultItem" type="radio" (click)="location = item">{{item}}</li>
      </ul>
    </div>

component

      ngOnInit(): void {
        this.search = fromEvent(document.getElementById('locationSearchInput'), 'input').pipe(
          debounceTime(750),
          distinctUntilChanged(),
          map((eventObj: Event) => (<HTMLInputElement>eventObj.target).value),
          switchMap((term: string) => this.cityService.getLocation(term)) <== should get called
        );
      }

test

      const cityServiceStub: CityService = jasmine.createSpyObj('CityService', ['getLocation']);
      ...
      it('should search for location on init', async(() => {
        const spy = (<jasmine.Spy>cityServiceStub.getLocation).and.returnValue(['Munich', 'Münster']);

        fixture.detectChanges();

        const rendered: DebugElement = fixture.debugElement.query(By.css('#locationSearchInput'));

        rendered.nativeElement.value = 'mun';
        rendered.nativeElement.dispatchEvent(new Event('input'));

        fixture.detectChanges();

        fixture.whenStable().then(() => {
          console.log(rendered.nativeElement.value);
          expect(spy).toHaveBeenCalledWith('mun');
        });
      }));

I've also tried to use fakeAsync with tick(750). But nothing helped. The console.log within the test also shows an empty string as input value. So maybe I'm simulating a wrong Event.

Stepan Suvorov
  • 25,118
  • 26
  • 108
  • 176
MarcoLe
  • 2,309
  • 6
  • 36
  • 74
  • Testing against Internet Explorer / Edge? – The Head Rush Apr 22 '19 at 15:55
  • In my karma.conf.js i use chrome – MarcoLe Apr 22 '19 at 17:14
  • Did you try to set `location` to the value `'mun'`? I wonder if in the detect changes process Angular is setting the value to an empty string because the `location` variable is an empty string. Since you have the `distinctUntilChanged()` in your pipe, it wouldn't have then changed since it started as an empty string and then was again after the debounce time. Have you tried to put a log statement at the beginning of your pipe to see what events are firing (if any)? i.e. `tap(e => console.log(e)),` before the `debounceTime(750),` – Daniel W Strimpel Apr 25 '19 at 01:06

3 Answers3

3

My test worked with following configuration:

  it('should search for location on init', fakeAsync(() => {
    const spy = (<jasmine.Spy>cityServiceStub.getLocation).and.returnValue(of(['Munich', 'Münster']));

    fixture.detectChanges();

    const rendered: DebugElement = fixture.debugElement.query(By.css('#locationSearchInput'));

    rendered.nativeElement.value = 'mun';
    rendered.nativeElement.dispatchEvent(new Event('input'));

    tick(750);
    fixture.detectChanges();

    expect(spy).toHaveBeenCalledWith('mun');
  }));

I missed just to return ['Munich', 'Münster'] as an Observable with the of() operator. And I should have used tick(750) to wait the specific amount of debounce time for an event change.

MarcoLe
  • 2,309
  • 6
  • 36
  • 74
0

Does this code work outside of the test? You haven't posted the whole component, but in the snippets you did post I cannot see a subscription being made anywhere. An Observable won't start emitting until it has at least one subscription (see here).

I implemented your test in my side project and it started working only after subscribing to the Observable, like so:

fromEvent(document.getElementById('locationSearchInput'), 'input').pipe(
  debounceTime(750),
  distinctUntilChanged(),
  map((eventObj: Event) => (<HTMLInputElement>eventObj.target).value),
  switchMap((term: string) => this.cityService.getLocation(term))
).subscribe()
  • Ure absolutely right. I subscribe to that observable in the html (Angular stuff)^^. I should have posted more markup to clarify this. Will edit my question accordingly. – MarcoLe Apr 26 '19 at 06:34
  • That makes sense, my test is still working though, so the event should be fine. Can you post the whole test setup? – Mikołaj Cepil Apr 26 '19 at 07:27
0

You should call .subscribe() for Observable. And you should return an Observable inside switchMap, so, you can't return an array with the spy, but Observable: const spy = (cityServiceStub.getLocation as jasmine.Spy).and.returnValue(of(['Munich', 'Münster']));

HTN
  • 3,388
  • 1
  • 8
  • 18
  • getLocation returns an observable... I setup wrongly the spy it should return `of(['Munich', 'Münster'])`. Maybe this is a mistake by me. I will test it today if it helps to solve my problem. – MarcoLe Apr 26 '19 at 07:13