Search code examples
angularunit-testingjasminetdd

How to unit test search functionality?


First off, i'm just new to unit testing or testing in general so i'm still wrapping my head around some concepts. Already got the basic of unit testing and I'm also currently learning about rxjs marble testing, so currently i'm stuck on how to test searching w/c functions the same way with the code in the documentation.

So Let's use the code from the tour of heroes in angular documentation

this.heroes$ = this.searchTerms.pipe(
  // wait 300ms after each keystroke before considering the term
  debounceTime(300),

  // ignore new term if same as previous term
  distinctUntilChanged(),

  // switch to new search observable each time the term changes
  switchMap((term: string) => this.heroService.searchHeroes(term)),
);

Here's my current test looks like

const searchResults$ = (inputs$: Observable<string>) =>
  inputs$.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap((term: string) => fakeHeroService.searchHeroes(term))
  );

  it('should return the correct results', () => {
    scheduler.run(helpers => {
      const { cold, hot, expectObservable } = helpers;

      // these are the search terms typed by user, observable
      // emitted by this.searchTerms
      const inputMarbles = '^-a 300ms b-c 300ms';

      // each emitted response by each service call
      const outputMarbles = '-- 300ms a-- 300ms c';

      // verify that service is called N times
      // verify that it's passed with certain argument per run


      searchServiceSpy = spyOn(heroService, 'searchHeroes').and.returnValue(
        cold(outputMarbles)
      );
      const source$ = hot(inputMarbles);

      const output$ = source$.pipe(searchResults$);

      expectObservable(output$).toBe(outputMarbles);

      /* expect(fakeSearchService.getMatches).toHaveBeenCalledTimes(2); */
    });
  });

I just can't get it working.


Solution

  • I suggest testing each part of the chain (debounce, disticnt, switchMap) in a separate test.

    You can use observer-spy with combination of fakeAsync for much easier tests.

    For example, to test the debounce you can write something like this (the following code might not be complete, but this is just to illustrate the point) -

    import {subscribeAndSpyOn} from '@hirez_io/observer-spy';
    
    it('should debounce by 300 ms', fakeAsync(() => {
    
      // basically with "debounce" you only care if 2 triggers happened and enough time passed, 
      // the number of results should be 1...
    
      const observerSpy = subscribeAndSpyOn(serviceUnderTest.heroes$);
      serviceUnderTest.searchTerms.next();
      tick(100);
      serviceUnderTest.searchTerms.next();
      tick(300);
      expect(observerSpy.getValuesLength).toBe(1);
    
    }));
    
    
    

    And write similar tests for the others that uses the "just enough principle" to setup the conditions for other operators in the chain.