dudewad
dudewad

Reputation: 13933

RxJs/Ngrx TestSheduler with simulated user input (Jasmine)

I'm testing Ngrx store effects in an angular application. One of my effects has, as a side effect, a modal that pops up using the material MatDialog component.

What I want to do is run a test where the effect is kicked off by an action, which triggers the mat dialog to appear. I then want to test clicking yes/no, and proceed with checking the resulting action (or lack thereof) afterwards.

We are using rxjs-marbles for testing our store.

RxJs doesn't exactly seem to have a huge abundance of examples, but I've gathered that I need to use the TestScheduler for this type of scenario. I'm not sure how it works, though. I'm doing lots of research and haven't made a ton of progress.

Basically, as a marble, it looks like this:

-a-b-c

where a is the initial action, b is the click from the user within the dialog, and c is the result (c is optional, if the user clicks 'no', then there will be no resultant action.

I'm not asking for anyone to write my code, just point me in the right direction. I'm about to start reading the source code for the TestScheduler to understand it better because I'm not even sure if it's what I should be using in this case.

Basically, how might one go about writing a test like this, where there are observable streams coupled with simulated user input using marbles?

Upvotes: 2

Views: 207

Answers (1)

dudewad
dudewad

Reputation: 13933

So I've realized I was going about this the wrong way. This is a unit test, not an e2e test, so I don't need to be testing specific functionality of the UI. I therefore came up with the following test:

it('should show a dialog, and do nothing if the user does not confirm',
      () => {
        const dialogRef = jasmine.createSpyObj('dialogRef', {
          afterClosed: of(false),
        });
        const action = new AppActions.PromptUserAction();
        matDialog.open.and.returnValue(dialogRef);

        actions = hot('a', { a: action });
        const expected = cold('', {});

        expect(effects.promptUserAction$).toBeObservable(expected);
        expect(matDialog.open).toHaveBeenCalled();
        expect(dialogRef.afterClosed).toHaveBeenCalled();
      });

In Explanation:

  • Create a dialogRef that will be returned by a mocked MatDialog service (done in the beforeEach Angular TestBed setup, created by providing a spy object like so:

    {
      provide: MatDialog,
      useValue: jasmine.createSpyObj(
        'MatDialog',
        ['open'],
      ),
    },
    
  • This stubs the open method, and you'll notice that the open call is made to returnValue dialogRef, which we can then use as a spy to return our desired observable. Pretty simple, really.

The rest of the test was straightforward. Hope this helps somebody -- you don't necessarily need to simulate the click since its implicit in the flow!

Upvotes: 3

Related Questions