Reputation: 12690
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?
Upvotes: 7
Views: 5622
Reputation: 496
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
UPDATE
I think it is time to update this a bit, both ngrx and rxjs have changed, the way I will check an observable is empty today will be by :
describe( 'showDialog$', () => {
it( 'should return an empty observable', async () => {
const dialogName = 'testDialog';
actions = of( showDialog( dialogName ) );
const result = await firstValueFrom( effects.showDialog$.pipe(isEmpty()) );
expect( result ).toEqual( true );
} );
} );
I don't tend to use Jasmine marbles any more, I generally only care about getting the last value of the effect and, jasmine marbles are overkill for that I only use them if I create custom operators, async await and firstValueFrom is generally better for most common cases, and for empty, just adding a .pipe(isEmpty()) does the trick well
Upvotes: 6
Reputation: 12156
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.
Upvotes: 2
Reputation: 58420
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.
Upvotes: 12