-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Comments
I think that will be a great addition, do you want to create a Pull Request for this @peterhe2000 ? |
@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. |
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 No worries. Go ahead. |
@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 I think you can override it with TestBed.overrideProvider(MoviesStore, {useValue: mockMoviesStore}); |
@timdeschryver Thank you, it works as expected. |
@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. |
Thank you so much @ngfelixl for getting this going! 🙏 |
@alex-okrushko Maybe off track a little bit. |
@peterhe2000 |
Is this still needed? |
Hi @ZackDeRose, I am waiting for a response by Alex. I think they are really busy with the v11 release. |
Makes sense! Thanks for the response @ngfelixl |
It would be very useful to me to have this kind of documentation for the component store. |
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:
How do one actually write a test for such selectors with |
Have you tried running your test in a |
@MotassemJa , Nope, but will try. Thanks. :) |
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 ? |
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');
});
});
}); |
@timdeschryver / @alex-okrushko / @ngfelixl How to mock component store effects ? any samples in github? |
Very much so. |
We are also struggeling with this at the moment. Any hints? |
Anyone got any answers for this one? Bonus if it uses Spectator for Angular. |
@erYasar, @jwedel , @MarkJPerry : In a component, where you want to test how your component reacts to different state of the component store? 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'); |
Thanks @JimMorrison723 ! I haven't come across jest-auto-spies before that's super helpful including the ObservableProps. Will deffo check it out tomorrow. |
Any update on this? |
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 |
There is store testing in the documentation:
https://ngrx.io/guide/store/testing
Is it possible to have something similar for component store?
The text was updated successfully, but these errors were encountered: