-
-
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
feature: Testing module for @ngrx/store #1027
Conversation
How could this be used to mock lazy-loaded feature state? The |
The purpose of this solution is to mock current state of the app, it doesn't care about the way the state was built. So it doesn't matter whether some part of state was built with |
85c6163
to
6eb8102
Compare
Hey @MikeRyanDev I've added a little bit more code and tests :) Can we work towards merging it? |
Additionally, to nextMock and spyOnDispatch, I think, it would be good to add an ability to mock selectors to the proposed MockStore implementation. Being able to mock selectors is mentioned quite often in the thread #915, so I think people like to use it. |
@TeoTN Thank you so much for that :) Is there any documentation/examples(i saw the tests)/practices for that? |
@TeoTN posting here because it will get lost in the issue. First, thanks for your work and patience! I've played around with this in the example app and I have some suggestions.
Here is a proposed update to the testing module. import { Injectable, Inject } from '@angular/core';
import {
StateObservable,
Store,
ReducerManager,
ActionsSubject,
ActionReducer,
Action,
ScannedActionsSubject,
INITIAL_STATE,
} from '@ngrx/store';
import { BehaviorSubject } from 'rxjs';
@Injectable()
export class MockState extends BehaviorSubject<any> {
constructor() {
super({});
}
}
@Injectable()
export class MockReducerManager extends BehaviorSubject<
ActionReducer<any, any>
> {
constructor() {
super(() => undefined);
}
}
@Injectable()
export class MockStore<T> extends Store<T> {
private state$ = new BehaviorSubject<T>(this.initialState as T);
constructor(
state$: StateObservable,
actionsObserver: ActionsSubject,
reducerManager: ReducerManager,
public scannedActions$: ScannedActionsSubject,
@Inject(INITIAL_STATE) private initialState: any
) {
super(state$, actionsObserver, reducerManager);
this.source = this.state$.asObservable();
}
setState(state: T): void {
this.state$.next(state);
}
dispatch<V extends Action = Action>(action: V) {
super.dispatch(action);
this.scannedActions$.next(action);
}
addReducer() {
// noop
}
removeReducer() {
// noop
}
}
export function provideMockStore<T = any>(config: { initialState?: T } = {}) {
return [
{ provide: INITIAL_STATE, useValue: config.initialState },
ActionsSubject,
ScannedActionsSubject,
{ provide: StateObservable, useClass: MockState },
{ provide: ReducerManager, useClass: MockReducerManager },
{
provide: Store,
useClass: MockStore,
},
];
} Thoughts? |
@brandonroberts your proposal for the testing module above seems like a perfect fit, the selectors do not need to be mocked; by providing state initially, the required selectors should be able to work with the store. That said, here are some suggestions to slightly enhance your proposal. the observable source field is now deprecated and in my opinion, i dont think it needs to be populated in this scenario.
please find below slight modification: import { Injectable, Inject } from '@angular/core';
import {
StateObservable,
Store,
ReducerManager,
ActionsSubject,
ActionReducer,
Action,
ScannedActionsSubject,
INITIAL_STATE,
} from '@ngrx/store';
import { BehaviorSubject } from 'rxjs';
@Injectable()
export class MockState<T> extends BehaviorSubject<T> {
constructor() {
super({} as T);
}
}
@Injectable()
export class MockReducerManager extends BehaviorSubject<
ActionReducer<any, any>
{
constructor() {
super(() => undefined);
}
}
@Injectable()
export class MockStore<T> extends Store<T> {
// not needed
// private state$ = new BehaviorSubject(this.initialState as T);
constructor(
state$: MockState<T>,
actionsObserver: ActionsSubject,
reducerManager: ReducerManager,
public scannedActions$: ScannedActionsSubject,
@Inject(INITIAL_STATE) private initialState: T
) {
super(state$, actionsObserver, reducerManager);
//deprecated
// this.source = this.state$.asObservable();
this.state$.next(this.initialState);
}
setState(state: T): void {
const newState = {
...this.initialState,
...state
};
this.state$.next(newState);
}
dispatch(action: V) {
super.dispatch(action);
this.scannedActions$.next(action);
}
addReducer() {
// noop
}
removeReducer() {
// noop
}
}
export function provideMockStore(config: { initialState?: T } = {} as T) {
return [
{ provide: INITIAL_STATE, useValue: config.initialState },
ActionsSubject,
ScannedActionsSubject,
MockState,
{ provide: ReducerManager, useClass: MockReducerManager },
{
provide: Store,
useClass: MockStore,
deps:[MockState]
},
];
} just a suggestion. Tested with my test suites and it works. Your thoughts? |
@ef32 for the most part it looks good to me. I'd rather leave it up the developer on the |
@brandonroberts true. The deps in the provider object isn’t needed. My bad , forgot to take it out during tests |
Hey, joining the discussion because there is one point where I have a different opinion:
and I think it might be worth to have a solution for that :) I've brought a piece code where we can manage and mock selectors really easily. Example: Let say you have a store which contains:
Based on some filters given by the user, you want to display for a given city, company and product category, the total sum of the products they have. In that case you'll have to provide a substantial amount of code/mocked data to have the right state. Where all you really want for this integration test is a number. |
nice example @maxime1992! i ll keep it in my hat |
Hey @brandonroberts, I like your ideas, especially getting isolated from any reducers. (I don't remember now why I've done it that way, maybe I missed some knowledge). Commit is on its way. :) |
6eb8102
to
eeecf78
Compare
623b275
to
71ca1a5
Compare
I'd appreciate some help with configuring Bazel properly... :) |
@@ -0,0 +1,12 @@ | |||
import { Injectable } from '@angular/core'; | |||
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The import here should just be rxjs
@@ -0,0 +1,9 @@ | |||
import { Injectable } from '@angular/core'; | |||
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The import here should just be rxjs
|
||
@Injectable() | ||
export class MockStore<T> extends Store<T> { | ||
private stateSubject = new BehaviorSubject<T>({} as T); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove stateSubject as it isn't needed?
public scannedActions$: Observable<Action>; | ||
|
||
constructor( | ||
private state$: StateObservable, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type with MockState<T>
instead of StateObservable
and remove the additional type casting below
modules/store/spec/store.spec.ts
Outdated
mockStore.dispatch(action); | ||
}); | ||
|
||
it('should allow setting a spy on dispatch method', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this test
Thanks for all your work on this @TeoTN! We will revisit mocking selectors since we are going to keep |
thank's for all this work |
Thank you @brandonroberts for all the help provided!! |
@nasreddineskandrani the testing in the example app has not been updated yet. |
hi @brandonroberts, --- [Doc] update This means the documentation can only be updated by you (the members of ngrx team) starting from v7? --- [example-app] update
do we want to change all tests to this way? |
@nasreddineskandrani no, it does not. Anyone can still edit the docs. They are just accessed from the site now. You can still edit any of the content for the docs in the All the test documentation will be updated to use the testing module. The testing guide will be updated with better documentation on testing. |
@brandonroberts i am checking some major issues in https://github.com/jest-community/vscode-jest |
Sure. The guide rewrites are being tracked here #1383 |
Addresses #915: This PR introduces a simple
MockStore
that allows setting state shape for testing purposes.Alongside with
MockStore
provider function is declared, and both are structured into@ngrx/store/testing
.Please review changes and help me ensure that the
testing
module is setup properly, or feel free to commit appropriate changes.