hmil / ng-vacuum

Hassle-free unit tests for Angular
https://code.hmil.fr/ng-vacuum/
3 stars 1 forks source link

Example with @ngrx/store #31

Open FDiskas opened 3 years ago

FDiskas commented 3 years ago

It would be nice to have an example with @ngrx/store

hmil commented 3 years ago

Hi @FDiskas ,

I don't use ngrx so I am not sure exactly how this would work.

From briefly skimming through the documentation it seems like you could mock the store like you'd mock any other service dependency.

It really depends on how you want to test your components.

NgVacuum defaults to isolating everything, this means that you'll have to test the reducer and the component separately.

I'm using the code on this page to illustrate this. Assuming you create a page object to interact with the DOM template, then you would mock the selectors and expect action dispatch like so:

let count$: Subject<number>;
function renderPage() {
    // Mock the selector (note: must be done before creating the component because this is called in the component constructor)
    count$ = new Subject<number>();
    when(getMock(Store).select('count')).useValue(count$);
    return new Page(renderComponent(MyCounterComponent, AppModule));
}

it('dispatches an action on click', fakeAsync(() => {
    const page = renderPage();
    // Expect the increment action to be dispatched 
    when(getMock(Store).dispatch(increment()).return().once();
    page.incrementButton.click();
}));

it('shows the current count', fakeAsync(() => {
    const page = renderPage();
    count$.next(2);
    page.detectChanges();
    expect(page.counter.innerText).toEqual('2');
}));

If this works and you interact a lot with rxjs stores, I suppose you could even write a small wrapper to increase readability of the tests:

it('dispatches an action on click', fakeAsync(() => {
    const page = renderPage();
    expectAction(increment()).once();
    page.incrementButton.click();
}));

function expectAction(action: Action) {
    return when(getMock(Store).dispatch(action)).return()
}

As for testing the reducer, it should be a pure function, in which case you can just test the reducer the old fashioned way, no need for ng-vacuum there or any other fancy utility:

it('increments the counter', () => {
    const initialState = 0;
    expect(counterReducer(initialState, increment())).toBe(1);
});

Alternatively, if you want to test the component together with the store (without isolating them) then you will have to prevent ng-vacuum from mocking the store.
This issue shows how you can do this for individual services. You would need to do something similar, but with the entire store module StoreModule.forRoot({ count: counterReducer }).
If you do find a convenient way to prevent automatic mocking of an entire module, then let me know. It can be a useful feature to add to the library.
But if this proves too difficult and you want to test your code this way, then using shallow-render directly could give you the kind of control you need. The only thing ng-vacuum provides is an opinionated way to use shallow-render and Omnimock, along with utilities to write tests in a systematic and consistent way. But you could easily write a similar library which better serves your opinions and workflows.