Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@ngrx/effects: testing an effect returns empty()

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?

like image 697
serlingpa Avatar asked Jan 07 '18 12:01

serlingpa


People also ask

How do you know if an observable array is empty?

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.

How does NgRx effect work?

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.

What are side effects in NgRx?

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.

What happens to observables emitted by ngrx effects when an error occurs?

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).

Where can I find documentation for the current version of ngrx?

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.

What are ngrx effects in Redux state?

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.”

How do I test a resort effect?

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.


3 Answers

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.

like image 98
cartant Avatar answered Oct 24 '22 08:10

cartant


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

like image 6
Gabriel Guerrero Avatar answered Oct 24 '22 07:10

Gabriel Guerrero


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.

like image 2
Klaster_1 Avatar answered Oct 24 '22 08:10

Klaster_1