Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I test an effect which has a debounce time?

I am using NgRx and want to test my effects. Some effects do have a debounce time. Like this example:

@Effect() searchImage$ = this.actions$.pipe( ofType(fromImageLibraryActions.SEARCH_IMAGES), map((action: fromImageLibraryActions.SearchImages) => action.query), debounceTime(300), switchMap(query: string) => this.imageLibraryService.getImagesBySearching(query)), map((images: LibraryImage[]) => new fromImageLibraryActions.LoadImages(images)));

How do I test them properly. I tried the following:

describe('SearchImages$', () => {
   it('should return loadImages action', fakeAsync(() => {
        const action = new fromImageLibraryActions.SearchImages('test');
        const images = [
            { uploaderId: 1 } as LibraryImage,
            { uploaderId: 2 } as LibraryImage
        ];

        const loadImagesAction = new fromImageLibraryActions.LoadImages(images);

        actions$ = hot('--a-', { a: action });

        tick(300);
        getTestScheduler().flush();

        const expected = cold('--b-', { b: loadImagesAction });
        expect(effects.searchImage$).toBeObservable(expected);          
   }));
});
like image 517
NiZa Avatar asked Oct 20 '25 20:10

NiZa


2 Answers

The ngrx examples do not work for me, so I created this video which explains how you can test ngrx effects with debounceTime.

The specific test in on GitHub, here, and looks like this:

import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { Observable } from 'rxjs';
import { loadClients, networkRequest } from './client.actions';
 
import { ClientEffects } from './client.effects';
 
describe('ClientEffects', () => {
  let actions$: Observable<any>;
  let effects: ClientEffects;
 
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        ClientEffects,
        provideMockActions(() => actions$)
      ]
    });
 
    effects = TestBed.inject<ClientEffects>(ClientEffects);
  });
 
  it('dispatch networkRequest', () => {
    const scheduler = getTestScheduler();
    scheduler.run(() => {
      // marbles
      const actions =  'a 500ms a b';
      const expected = '500ms a - 500ms b';
 
      actions$ = hot(actions, {
        a: loadClients(),
        b: loadClients(),
      });
 
      expect(effects.loadClients$).toBeObservable(cold(expected, {
        a: networkRequest(),
        b: networkRequest(),
      }));
    });
  });
});
like image 184
Ian Jamieson Avatar answered Oct 23 '25 11:10

Ian Jamieson


I like to use fake timers and just skip the time, see my blog for more details https://timdeschryver.dev/blog/testing-an-ngrx-project#effect-tests-and-fake-timers.

afterEach(() => {
    // don't forget to reset the timers
    jest.useRealTimers();
});

it('fetch$ dispatches success action with fake timers', () => {
    jest.useFakeTimers();

    const actions = new ActionsSubject();
    const effects = new WerknemersEffects(actions, getMockStore(), newWerknemerService());

    const result: Action[] = [];
    effects.fetch$.subscribe((action) => {
        result.push(action);
    });

    const action = werknemerActions.missingWerknemerOpened({ werknemerId: 3 });
    actions.next(action);

    jest.advanceTimersByTime(10_000);

    // 🔦 to make tests less brittle, wait for the task to finish with `runOnlyPendingTimers` or `runOnlyPendingTimers` instead of advancing the time with `advanceTimersByTime`.
    // This makes sure that the test isn't impacted when the duration is modified.
    jest.runOnlyPendingTimers();

    expect(result).toEqual([
        werknemerActions.fetchWerknemerSuccess({
            werknemer: newWerknemer({ id: action.werknemerId }),
        }),
    ]);
});

like image 42
timdeschryver Avatar answered Oct 23 '25 12:10

timdeschryver