I am using @ngrx/effects 4.1.1. I have an effect that returns an empty observable like this:
@Effect() showDialog$: Observable<Action> = this
.actions$
.ofType( ActionTypes.DIALOG_SHOW )
.map( ( action: DialogAction ) => action.payload )
.switchMap( payload => {
this.dialogsService.showDialog( payload.className );
return empty();
} );
I am trying to write a unit test following these guidelines that will test that the effect yields an empty observable. I have this:
describe( 'DialogEffects', () => {
let effects: DialogEffects;
let actions: Observable<any>;
const mockDialogService = {
showDialog: sinon.stub()
};
beforeEach( () => {
TestBed.configureTestingModule( {
providers: [
DialogEffects, provideMockActions( () => actions ),
{
provide: DialogsService,
useValue: mockDialogService
}
]
} );
effects = TestBed.get( DialogEffects );
} );
describe( 'showDialog$', () => {
it( 'should return an empty observable', () => {
const dialogName = 'testDialog';
const action = showDialog( dialogName );
actions = hot( '--a-', { a: action } );
const expected = cold( '|' );
expect( effects.showDialog$ ).toBeObservable( expected );
} );
} );
} );
However, Karma (v1.7.1) complains:
Expected [ ] to equal [ Object({ frame: 0, notification: Notification({ kind: 'C', value: undefined, error: undefined, hasValue: false }) }) ].
How do I test that the effect returns empty()
? I have tried modifying the effect metadata using dispatch: false
, but this has no effect.
Ideas?
The RxJS isEmpty() operator returns an observable with a Boolean value indicating whether the observable was empty or not. It returns an output as true if the source observable is empty; otherwise, false. Let us see some examples of the RxJS isEmpty() operator to understand it clearly.
Most effects are straightforward: they receive a triggering action, perform a side effect, and return an Observable stream of another action which indicates the result is ready. NgRx effects will then automatically dispatch that action to trigger the reducers and perform a state change.
A side effect refers simply to the modification of some kind of state - for instance: Changing the value of a variable; Writing some data to disk; Enabling or disabling a button in the User Interface.
If we do that, the Observable that NgRx Effects subscribes to is going to be replaced by the Observable emitted in the catchError method, and then all future values emitted from action$ are going to be ignored (the new Observable in the error handler is not subscribed to them).
Please visit ngrx.io to see documentation for the current version of NgRx. An Effect subscribes to the Actions Observable to perform side effects. provideMockActions provides a mock provider of the Actions Observable to subscribe to, for each test individually. Later in the test cases, we assign the actions$ variable to a stream of actions.
One thing that took a while for us to get our heads around is NgRx Effects. It’s how you deal with asynchronous actions in Redux state; in short, it gives you a hook into your Redux store to say “every time an event is dispatched, after it’s been reduced, let me know.”
Open the src/app/state/resort/resort.effects.spec.ts file. Before we can test an effect we need to do a little setup for our tests using the beforeEach () method: Define actions block scope variable within the describe callback function that is an Observable. This observable stream will represent the actions dispatched by the store.
The problem is that you are comparing the actual result against cold('|')
.
The pipe character in cold('|')
represents the completion of the observable stream. However, your effect will not complete. The empty()
observable does complete, but returning empty()
from switchMap
just sees that observable merged into the effect observable's stream - it does not complete the effect observable.
Instead, you should compare the actual result against cold('')
- an observable that emits no values and does not complete.
The best way to do this is to use dispatch false in the effect and change the swicthMap for a tap
@Effect({dispatch: false}) showDialog$ = this.actions$.pipe(
ofType( ActionTypes.DIALOG_SHOW ),
map( ( action: DialogAction ) => action.payload ),
tap( payload =>
this.dialogsService.showDialog( payload.className )
));
To test it you can do like
describe( 'showDialog$', () => {
it( 'should return an empty observable', () => {
const dialogName = 'testDialog';
const action = showDialog( dialogName );
actions = hot( '--a-', { a: action } );
expect(effects.showDialog$).toBeObservable(actions);
// here use mock framework to check the dialogService.showDialog is called
} );
} );
Notice the reason I use toBeObservable(actions) thats because due to disptach false this effect will not produce anything so actions is the same as before , what you gain by calling toBeObservable(actions) is that is makes the test synchronous, so you can check after it that your service was called with a mock framework like jasmine or ts-mockito
To test that effect does not dispatch, you can assert against it's metadata with getEffectsMetadata function:
expect(getEffectsMetadata(effects).deleteSnapshotSuccess$?.dispatch).toBe(false)
The above has been tested with ngrx 9.0.0. Since all observables emit regardless of dispatch
, checks like expect(effects.effect$).toBeObservable(cold(''))
won't produce expected results.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With