Search code examples
angularkarma-jasminengrxngrx-store

How to test if action is dispatched in NgRX?


I have a component which is dispatched actions in constructor:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {    
  constructor(store: Store<State>) {
    store.dispatch(action1());
    store.dispatch(action2());
  }
}

I need to test if action1 and action2 is dispatched. I'm trying to do it with MockStore:

import {MockStore, provideMockStore} from '@ngrx/store/testing';

let store: MockStore;

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [
      AppComponent
    ],
    providers: [
      provideMockStore({ initialState }),
    ],
  }).compileComponents();
  store = TestBed.inject(MockStore);
});

And here is my tests:

it('should dispatch an action1 in constructor', () => {
  TestBed.createComponent(AppComponent);

  const expected = cold('a', {a: action1()});

  expect(store.scannedActions$).toBeObservable(expected);
});

it('should dispatch an action2 in constructor', () => {
  TestBed.createComponent(AppComponent);

  const expected = cold('a', {a: action2()});

  expect(store.scannedActions$).toBeObservable(expected);
});

This is strange but only one test is passed. It depends on the order of the dispatch() calls.

store.scannedActions$ contains only one value. If component code is:

constructor(store: Store<State>) {
  store.dispatch(action1());
  store.dispatch(action2());
}

then store.scannedActions$ contains only action2()

if component code is:

constructor(store: Store<State>) {
  store.dispatch(action2());
  store.dispatch(action1());
}

then store.scannedActions$ contains only action1()

How to test both actions?


Solution

  • It seems like scannedActions$ even though it is plural (actions) only has the last Action dispatched stored looking at its interface.

    I would just use spyOn and spy on store.dispatch and see if it was called with the right actions.

    import {MockStore, provideMockStore} from '@ngrx/store/testing';
    
    let store: MockStore;
    
    beforeEach(async () => {
      await TestBed.configureTestingModule({
        declarations: [
          AppComponent
        ],
        providers: [
          provideMockStore({ initialState }),
        ],
      }).compileComponents();
      store = TestBed.inject(MockStore);
    });
    
    it('should dispatch action1 and action 2 in constructor', () => {
      const dispatchSpy = spyOn(store, 'dispatch').and.callThrough(); // spy on the store 
      // dispatch but still call the actual implementation
      TestBed.createComponent(AppComponent);
      fixture.detectChanges(); // used to call ngOnInit, maybe it is not needed
    
      expect(dispatchSpy).toHaveBeenCalledWith(action1());
      expect(dispatchSpy).toHaveBeenCalledWith(action2());
    });