platform icon indicating copy to clipboard operation
platform copied to clipboard

Is it possible to add component-store test example to documentation?

Open peterhe2000 opened this issue 3 years ago • 29 comments

There is store testing in the documentation:
https://ngrx.io/guide/store/testing Is it possible to have something similar for component store?

peterhe2000 avatar Oct 31 '20 10:10 peterhe2000

I think that will be a great addition, do you want to create a Pull Request for this @peterhe2000 ?

timdeschryver avatar Nov 10 '20 10:11 timdeschryver

@timdeschryver Thanks for asking. Tim. Happy to leave opptunities to others since i have not yet used component store in a real project and don't have much experience with it.

peterhe2000 avatar Nov 10 '20 10:11 peterhe2000

Hi @peterhe2000 @timdeschryver, I would be very happy to help adding the testing docs for the component store. Is it ok if I go for it?

ngfelixl avatar Nov 13 '20 21:11 ngfelixl

@ngfelixl No worries. Go ahead.

peterhe2000 avatar Nov 14 '20 01:11 peterhe2000

@timdeschryver do you know a way how to override an Injectable that is provided in the components providers array and not in the corresponding module. The problem is that the component store is provided in that way, and it makes most sense. But to do the integration tests for the component, it would be nice to provide a way to mock the store and to get a reference to work with it.

The components decorator looks like:

@Component({
  selector: 'app-movies',
  templateUrl: './movies.component.html',
  styleUrls: ['./movies.component.css'],
  providers: [MoviesStore] // <-- Here we provide the MoviesStore to the component
})

And the TestBed configuration like this:

let mockMoviesStore = jasmine.createSpyObj('MoviesStore', ['setState', 'addMovie'], ['movies$']);
[...]
await TestBed.configureTestingModule({
  declarations: [ MoviesComponent ],
  providers: [{ // <-- This won't work, because it overrides the module moviesstore, not the component moviesstore
    provide: MoviesStore,
    useValue: mockMoviesStore
  }],
});

If the MoviesStore is provided in the module, it works.

ngfelixl avatar Nov 18 '20 09:11 ngfelixl

@ngfelixl I think you can override it with

   TestBed.overrideProvider(MoviesStore, {useValue: mockMoviesStore});

timdeschryver avatar Nov 18 '20 13:11 timdeschryver

@timdeschryver Thank you, it works as expected.

ngfelixl avatar Nov 18 '20 13:11 ngfelixl

@timdeschryver I've set up a repository with a sample app that includes the read/write pages from the ngrx.io docs and the corresponding tests here. The github actions are set up and run the tests.

The tests are separated into two types of tests. One is the component integration test with the TestBed using a mockComponentStore which describes all the features related to the DOM, the FormGroup and makes sure that all the corresponding actions of the component store are dispatched with the correct values.

The other one tests the component store. It creates an instance and tests all the reducers (updaters) and selectors using the top level function setState to arrange an initial state, and the observable state$ to assert the expectations.

Would you like to have a look at it? Then I could start to set up the testing page in the docs. Please note that currently the effects are not covered yet. Thanks in advance 🙏

ngfelixl avatar Nov 18 '20 19:11 ngfelixl

Thank you so much @ngfelixl for getting this going! 🙏 I'll peeked into the suggested tests and would provide some feedback a bit later.

alex-okrushko avatar Nov 18 '20 19:11 alex-okrushko

@alex-okrushko Maybe off track a little bit. I am curious to know how firebase team do form validation with component store. Are you guys using template driven form validation or reactive form validation?

peterhe2000 avatar Nov 18 '20 22:11 peterhe2000

@peterhe2000 let's take that discussion to https://discord.gg/ngrx :)

alex-okrushko avatar Nov 22 '20 17:11 alex-okrushko

Is this still needed?

ZackDeRose avatar Jan 14 '21 08:01 ZackDeRose

Hi @ZackDeRose, I am waiting for a response by Alex. I think they are really busy with the v11 release.

ngfelixl avatar Jan 14 '21 09:01 ngfelixl

Makes sense! Thanks for the response @ngfelixl

ZackDeRose avatar Jan 14 '21 15:01 ZackDeRose

It would be very useful to me to have this kind of documentation for the component store.

fgoulet avatar Feb 20 '21 14:02 fgoulet

Would be really good to have a guide/best practice for testing the component store.

At NgRx homepage one can read under the Debounce selectors section:

...This makes it harder to test such selectors.

How do one actually write a test for such selectors with { debounce: true }, as well as the anonymous/no-name effects added in the constructor? If you could share some knowledge about this @alex-okrushko, it would be greatly appreciated!

whernebrink avatar Mar 02 '21 22:03 whernebrink

Would be really good to have a guide/best practice for testing the component store.

At NgRx homepage one can read under the Debounce selectors section:

...This makes it harder to test such selectors.

How do one actually write a test for such selectors with { debounce: true }, as well as the anonymous/no-name effects added in the constructor? If you could share some knowledge about this @alex-okrushko, it would be greatly appreciated!

Have you tried running your test in a fakeAsync? You need then to use flush() right after you set your state$ and before running any effects.

MotassemJa avatar Jun 22 '21 09:06 MotassemJa

@MotassemJa , Nope, but will try. Thanks. :)

whernebrink avatar Jun 22 '21 09:06 whernebrink

Hi, I'm using component-store for the first time. Everything is going well, I'm now getting to the unit tests.

The component is good, but I would like to have an example with marble to test the store.

Do you think it's possible ?

kekel87 avatar Nov 04 '21 14:11 kekel87

Okay, I managed to test my Store.

I'll share my code with you, if it helps or if you have any feedback:

@Injectable({ providedIn: 'root' })
export class MyStore extends ComponentStore<MyState> {
  readonly data$: Observable<any[]> = this.select((state) => state.data);
  readonly requestState$: Observable<RequestState> = this.select((state) => state.requestState);

  constructor(private myService: MyService) {
    super({ data: [], requestState: RequestState.Initial });
  }

  readonly searchData = this.effect((search$: Observable<string>) =>
    search$.pipe(
      tap(() => this.onSearchData()),
      switchMap((search) =>
        this.myService.list(search).pipe(
          tapResponse(
            (paginated) => this.onSearchDataSuccess(paginated.items),
            (_: HttpErrorResponse) => this.onSearchDataError()
          )
        )
      )
    )
  );

  private readonly onSearchData = this.updater((state) => ({
    ...state,
    requestState: RequestState.Loading,
  }));

  private readonly onSearchDataSuccess = this.updater((state, datasets: ListItem[]) => ({
    ...state,
    datasets,
    requestState: RequestState.Success,
  }));

  private readonly onSearchDataError = this.updater((state) => ({
    ...state,
    datasets: [],
    requestState: RequestState.Error,
  }));
}
describe('MyStore', () => {
  const myService = jasmine.createSpyObj<MyService>('MyService', ['list']);
  let store: MyStore;

  beforeEach(() => {
    store = new MyStore(myService);
  });

  it('should have initial state', () => {
    expect(store.requestState$).toBeObservable(cold('a', { a: RequestState.Initial }));
    expect(store.data$).toBeObservable(cold('a', { a: [] }));
  });

  describe('searchData effect', () => {
    it('should search data and set data and request state', () => {
      myService.list.and.returnValue(cold('-----a-|', { a: MockPaginated.listItem }));

      store.searchData('search');

      expect(merge(store.requestState$, store.data$)).toBeObservable(
        cold('(ab)-(cd)', {
          a: RequestState.Loading,
          b: [],
          c: RequestState.Success,
          d: MockPaginated.listItem.items,
        })
      );

      expect(myService.list).toHaveBeenCalledWith('search');
    });

    it('should handle error', () => {
      myService.list.and.returnValue(cold('-----#', undefined, MockHttpErrorResponse.base));

      store.searchData('search');

      expect(merge(store.requestState$, store.data$)).toBeObservable(
        cold('(ab)-(cd)', {
          a: RequestState.Loading,
          b: [],
          c: RequestState.Error,
          d: [],
        })
      );

      expect(myService.list).toHaveBeenCalledWith('search');
    });
  });
});

kekel87 avatar Nov 04 '21 15:11 kekel87

@timdeschryver / @alex-okrushko / @ngfelixl

How to mock component store effects ? any samples in github?

erYasar avatar Jul 05 '22 04:07 erYasar

Is this still needed?

Very much so.

moniuch avatar Oct 02 '22 11:10 moniuch

@timdeschryver / @alex-okrushko / @ngfelixl

How to mock component store effects ? any samples in github?

We are also struggeling with this at the moment. Any hints?

jwedel avatar Mar 23 '23 08:03 jwedel

Anyone got any answers for this one? Bonus if it uses Spectator for Angular.

MarkJPerry avatar Apr 27 '23 13:04 MarkJPerry

@erYasar, @jwedel , @MarkJPerry : In a component, where you want to test how your component reacts to different state of the component store? Mock it, as you would mock any other observable. Here is in example (based on previous code) with Jest and jest-auto-spies:

let storeSpy: Spy<MyStore>;
....
storeSpy = createSpyFromClass(MyStore, {
    methodsToSpyOn: ['searchData'],
    observablePropsToSpyOn: ['state$'],
});
storeSpy.state$.nextWith(RequestState.Initial);

TestBed.overrideProvider(MyStore, { useValue: storeSpy });

Update the component store in the beforeEach block based on your scenario:

storeSpy.state$.nextWith(RequestState.Loading);

And then you can test if method has been called, etc

expect(storeSpy.searchData).toHaveBeenCalledTimes(1);
expect(storeSpy.searchData).toHaveBeenCalledWith('test');

JimMorrison723 avatar Apr 27 '23 15:04 JimMorrison723

Thanks @JimMorrison723 ! I haven't come across jest-auto-spies before that's super helpful including the ObservableProps. Will deffo check it out tomorrow.

MarkJPerry avatar Apr 27 '23 16:04 MarkJPerry

Any update on this?

gregarega1993 avatar May 29 '23 10:05 gregarega1993

I created a simple article about this topic if anyone finds it useful: https://medium.com/@rogerg93/how-to-unit-test-the-ngrx-component-store-in-angular-3ad395a21cbd

Also any feedback is appreciated.

Let me know if this is something that could be added as a guide in the official documentation or is too specific cause it's using Jasmine. I can help with the documentation I would just need some guidance.

gregarega1993 avatar Jun 05 '23 09:06 gregarega1993

I created a simple article about this topic if anyone finds it useful: https://medium.com/@rogerg93/how-to-unit-test-the-ngrx-component-store-in-angular-3ad395a21cbd

Also any feedback is appreciated.

Let me know if this is something that could be added as a guide in the official documentation or is too specific cause it's using Jasmine. I can help with the documentation I would just need some guidance.

Nice, good resource. :) One thing to add could be testing selectors with { debounce: true } (if that requires some changes to the tests), other than that it's looking nice. :)

whernebrink avatar Jun 05 '23 09:06 whernebrink