Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
peterhe2000 opened this issue Oct 31, 2020 · 29 comments · May be fixed by #3646
Open

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

peterhe2000 opened this issue Oct 31, 2020 · 29 comments · May be fixed by #3646

Comments

@peterhe2000
Copy link

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

@timdeschryver
Copy link
Member

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

@peterhe2000
Copy link
Author

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

@ngfelixl
Copy link
Contributor

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?

@peterhe2000
Copy link
Author

@ngfelixl No worries. Go ahead.

@ngfelixl
Copy link
Contributor

ngfelixl commented Nov 18, 2020

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

@timdeschryver
Copy link
Member

@ngfelixl I think you can override it with

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

@ngfelixl
Copy link
Contributor

@timdeschryver Thank you, it works as expected.

@ngfelixl
Copy link
Contributor

ngfelixl commented Nov 18, 2020

@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 🙏

@alex-okrushko
Copy link
Member

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

@peterhe2000
Copy link
Author

@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?

@alex-okrushko
Copy link
Member

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

@ZackDeRose
Copy link
Contributor

Is this still needed?

@ngfelixl
Copy link
Contributor

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

@ZackDeRose
Copy link
Contributor

Makes sense! Thanks for the response @ngfelixl

@fgoulet
Copy link

fgoulet commented Feb 20, 2021

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

@whernebrink
Copy link

whernebrink commented Mar 2, 2021

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!

@MotassemJa
Copy link

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.

@whernebrink
Copy link

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

@kekel87
Copy link

kekel87 commented Nov 4, 2021

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
Copy link

kekel87 commented Nov 4, 2021

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');
    });
  });
});

@erYasar
Copy link

erYasar commented Jul 5, 2022

@timdeschryver / @alex-okrushko / @ngfelixl

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

@moniuch
Copy link
Contributor

moniuch commented Oct 2, 2022

Is this still needed?

Very much so.

@BobobUnicorn BobobUnicorn linked a pull request Nov 3, 2022 that will close this issue
3 tasks
@jwedel
Copy link

jwedel commented Mar 23, 2023

@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?

@MarkJPerry
Copy link

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

@JimMorrison723
Copy link
Contributor

JimMorrison723 commented Apr 27, 2023

@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');

@MarkJPerry
Copy link

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

@gregarega1993
Copy link

Any update on this?

@gregarega1993
Copy link

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.

@whernebrink
Copy link

whernebrink commented Jun 5, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.