[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
There's no simple way to mock a Store for testing purposes.
When testing a component or an effect, I'd like to be able to inject a fake Store to the tested entity. It became increasingly non-trivial after the pipable operators were introduced.
For instance, one may want to test behavior component with a given state that is a result of many various actions. Similarly, one may want to test an effect with various fixtures for state.
Across the web there are multiple snippets with custom implementation of MockStore that extends either Store or BehaviorSubject but all of them are outdated. The presence of such snippets suggests though that there's a real need for some testing utils.
I propose introduction of @ngrx/store/testing module which would contain provideMockStore for providing MockStore in place of Store and an implementation of MockStore that provides nextMock method that replaces current state just like BehaviorSubject.next does on Subject.
Newest ngrx, reasonably new npm, node, Chrome and Windows.
I'll make a contribution with MockStore class and a provider, if you feel it's a valid issue.
Great suggestion! @brandonroberts and I have a mock Store implementation we use internally that works similarly to how you propose. It should also setup Store#dispatch to be a spy.
Great proposal! IMHO will be nice to have a separate package inside platform with all testing helper tools.
Yeah, We really need it!
This has been raised a number of times already and I'd like to get the consensus whether mock Store is needed or not :)
E.g. in https://github.com/ngrx/store/issues/128 @robwormald says:
_If you want to implement a mock version (which, IMHO, is entirely unnecessary, but feel free...)_
The same recommendation is at current testing guide for the store https://github.com/ngrx/platform/blob/master/docs/store/testing.md#providing-store-for-testing
Reducing state is synchronous, so mocking out the Store isn't required.
Internally at Google I've been recommending to avoid mocking the Store as well - my suggestion was to use the real Store and if it's needed to be in certain state for the tests to just dispatch the sequence of actions. But we are using EffectsRunner copied from the previous version of ngrx/effects, so that might be helping us.
@alex-okrushko I am perfectly aware of the fact the store is synchronous.
However in my opinion, dispatching a sequence of actions (possibly wrapped in effects) only for testing something completely else, is simply writing code that serves no purpose. I think of it as just working around the fact that we have no possibility to simply pass an object with the state snapshot for tests.
It's probably a waste of time, programmers' keyboards and CPUs power ;)
I believe that unit tests should just verify very narrow scope of reality and we shouldn't really need to build boilerplate code to make them run, right?
@alex-okrushko We can't all be Rob Wormalds. 馃槈
Whether a mock store is necessary or unnecessary, I think _at the very least_, the documentation for unit testing around ngrx should define a much broader scope than is currently provided in the testing guide.
The attraction for a mock store for me is ease of use and simplicity. If there is a mock that can be provided in my spec classes that reduces the friction for setting up spies, establishing needed mock state, and testing with effects easier, count me in. Maybe I'm lazy, or I don't understand the inner workings of ngrx sufficiently, but I'd like to make the task of writing tests near brainless. Don't make me think too hard just to scaffold out tests.
Short of providing a mock, better documentation would be great around different scenarios. If that documentation exists... a more obvious link would be great.
I'd appreciate a complete testing guide. Testing every part of the store (including router state) AND inclusion in components throught selectors. Testing documention is almost non existent.
It's not a full-fledged MockStore, but it's helped quite a bit when I was testing container components that were subscribed via different store selectors. We had issues where we had to initialise nested state in the TestBed, and we already had separate selector tests to cover the selector logic.
The MockStore implementation I wrote allows manual publishing of events per selector, which was useful in testing different permutations in the container component.
// in container component
ngOnInit() {
this.list$ = this.store.select(getList);
this.pending$ = this.store.select(getPending);
this.error$ = this.store.select(getError);
}
// in beforeEach
const mockStore = new MockStore({ getList, getPending, getError });
spyOn(mockStore, 'select').and.callThrough();
spyOn(mockStore, 'dispatch').and.callThrough();
TestBed.configureTestingModule({
providers: [
{ provide: Store, useValue: mockStore }
]
}
// in test
it('should not show list when pending', () => {
mockStore.selectStream['getList'].next([{ foo: 'bar' }]);
mockStore.selectStream['getPending'].next(true);
mockStore.selectStream['getError'].next(false);
fixture.detectChanges();
// do assertions
});
Here are the links to the implementation (my own sandbox repo outside of work).
Hope this helps while an official MockStore is being worked on!
@alex-okrushko
Internally at Google I've been recommending to avoid mocking the Store as well - my suggestion was to use the real Store and if it's needed to be in certain state for the tests to just dispatch the sequence of actions
I am doing it the way you are describing by triggering actions... i don't feel this is the right way (it's eurkk ).
Example: (why i am saying this way is eurkk ^^!!)
If you have a common component you need to use in different pages/features. Which action you pick in the tests to set the right state in store (the one from pageA, pageB, featureC, ...)?
What happen if you pick pageA action and the product team decide to remove pageA later during the year...? (time lost at least)
I think from what i see in @TeoTN or @honsq90 propositions, there are benefits of mocking directly the store/selectors vs using actions:
@TeoTN
i didn't check the PR and test it yet. You are proposing nextMock to update the whole store?
seems @honsq90 already implemented _a more flexible mocking strategy at first view_. Didn't try yet. What do you think about it?
@honsq90 Solution however will not work on the new pipeable select
@nasreddineskandrani
Well, yes, the idea is to use nextMock to update the whole state. Generally, as with all PRs there's an underlying problem I was trying to solve: in a bit more complex applications, building up a desired state for testing would require quite a lot of effort, e.g. dispatching multiple actions etc. Hence, my idea was to be able to simply bootstrap the whole state for testing. Such mocked state could be also extracted and stored in a separate file.
I rely on the fact that State is actually a Subject.
I want to be extra clear on that: it's a dead simple, hammer-like solution. But I'm open for extensions ;)
so @TeoTN can we build a solution to mock selector directly. It's more powerfull, No?
I am ready to close this... we need a focused effort to decice and update the code to have it next release PLEASE ^^
@nasreddineskandrani that's an interesting idea but what if someone's not using the selector? I mean, it's a layer above the single source of truth and technically one may decide not to use it
@nasreddineskandrani I've trying to mock the selectors for a whole day without any success.
The new pipeable select is just pain in the ass to mock :/
@TeoTN if he don't use selector from ngrx you mean => I guess he will need to find his own way of testing also :D
@Juansasa Sorry i am starting a new job. A lot of efforts... I ll try when i stabilize if no one finish it yet. In around 2 weeks maximum.
@nasreddineskandrani lol, that's so inclusive approach
Joining the discussion here after I upgraded a huge project to use the pipeable operator.
I've made my own implementation of MockStore:
export class MockStore<T extends RootState = RootState> extends BehaviorSubject<T> {
dispatch = jasmine.createSpy();
}
But realised that it is far from ideal when dealing with unit tests (whereas it's just fine for integration tests where you provide the initial state).
For unit tests, I want to be able to return directly a value for a given selector.
So with the help of @zakhenry I came up with the following:
export class MockStore<StateType extends RootState = RootState> extends BehaviorSubject<StateType> {
private selectorsToValues: Map<(...args: any[]) => any, any> = new Map();
public dispatch = jasmine.createSpy();
constructor(initialState: StateType = null, private returnNullForUnhandledSelectors = true) {
super(initialState);
spyOnProperty(ngrx, 'select').and.callFake(_ => {
return selector => {
let obs$: Observable<any>;
if (this.selectorsToValues.has(selector)) {
const value = this.selectorsToValues.get(selector);
obs$ = value instanceof Observable ? value : this.pipe(map(() => value));
}
obs$ = this.pipe(map(() => (this.returnNullForUnhandledSelectors ? null : selector(this.getValue()))));
return () => obs$.pipe(distinctUntilChanged());
};
});
}
addSelectorStub<T>(cb: (...args: any[]) => T, mockedValue: T | Observable<T>): this {
this.selectorsToValues.set(cb, mockedValue);
return this;
}
setState(state: StateType): this {
this.next(state);
return this;
}
setReturnNullForUnandledSelectors(value: boolean): this {
this.returnNullForUnhandledSelectors = value;
return this;
}
}
It's very fresh and might need some more work.
Also, notice the spyOnProperty(ngrx, 'select'), I've imported ngrx like that:
import * as ngrx from '@ngrx/store';
@maxime1992 Nice spyOnProperty is precisely what i missed, didnt know there are such method in jasmine 馃憤
@Juansasa took me a while to figure that out too. Had no idea it existed.
Good thing is, I've learned quite a few things in the research process!
With spyOnProperty you can also spy/stub getters and setters :zap:
It seems that the default is setting the get so I just took it out but you can also do spyOnProperty(ngrx, 'select', 'get')
Is there any equivalent on jest?
@blackholegalaxy maybe https://github.com/phra/jest/commit/1a3b82a949776971075efeb08210e1bd464a11de
@maxime1992 can you please provide an example of your mockstore on stackblitz?
@maxime1992 nice! Does it work universally with the traditional and also pipeable select?
Also I think the
obs$ = this.pipe(map(() => (this.returnNullForUnhandledSelectors ? null : selector(this.getValue()))));
should be wrapped in the else clause.
@maxime1992 I really like your implementation, I have added the store() function to your MockStore code, so is should work with the classic as well as pipeable select operator.
and I have wrapped this piece of code in the else clause:
obs$ = this.pipe(map(() => (this.returnNullForUnhandledSelectors ? null : selector(this.getValue()))));
Now it looks like (code based on code of @maxime1992 from few posts earlier):
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { AppState } from '@app/reducers.index';
import { map, distinctUntilChanged } from 'rxjs/operators';
import * as ngrx from '@ngrx/store';
@Injectable()
export class MockStore<StateType extends AppState = AppState> extends BehaviorSubject<StateType> {
private selectorsToValues: Map<(...args: any[]) => any, any> = new Map();
public dispatch = jasmine.createSpy();
public select = jasmine.createSpy().and.callFake(
(selector: any): Observable<any> => {
return this.getObservableWithMockResult(selector).pipe(distinctUntilChanged());
}
);
constructor(initialState: StateType = null, private returnNullForUnhandledSelectors = true) {
super(null);
spyOnProperty(ngrx, 'select').and.callFake(_ => {
return selector => {
return () => this.getObservableWithMockResult(selector).pipe(distinctUntilChanged());
};
});
}
private getObservableWithMockResult(selector:any):Observable<any>{
let obs$: Observable<any>;
if (this.selectorsToValues.has(selector)) {
const value = this.selectorsToValues.get(selector);
obs$ = value instanceof Observable ? value : this.pipe(map(() => value));
} else {
obs$ = this.pipe(map(() => (this.returnNullForUnhandledSelectors ? null : selector(this.getValue()))));
}
return obs$;
}
addSelectorStub<T>(cb: (...args: any[]) => T, mockedValue: T | Observable<T>): this {
this.selectorsToValues.set(cb, mockedValue);
return this;
}
setState(state: StateType): this {
this.next(state);
return this;
}
setReturnNullForUnandledSelectors(value: boolean): this {
this.returnNullForUnhandledSelectors = value;
return this;
}
}
I am using it in tests like this at the moment:
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
import { StoreModule, Store } from '@ngrx/store';
import { MockStore } from '@app/test-utils/mockup-store';
describe('SomeComponent', () => {
let component: SomeComponent;
let fixture: ComponentFixture<SomeComponent>;
let mockStore: MockStore<ExtendedStateWithLazyLoadedFeatures>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SomeComponent],
imports: [StoreModule.forRoot({})],
providers: [{ provide: Store, useValue: new MockStore<ExtendedStateWithLazyLoadedFeatures>(null, true) }]
}).compileComponents();
}));
beforeEach(inject([Store], (testStore: MockStore<ExtendedStateWithLazyLoadedFeatures>) => {
// save the automatically injected value so we can reference it in later tests
mockStore = testStore;
}));
describe('with values for selectors defined', () => {
beforeEach(() => {
mockStore.addSelectorStub(selector1, {});
mockStore.addSelectorStub(selector2, ["mockValue"]);
});
beforeEach(() => {
fixture = TestBed.createComponent(SomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
// this is checking if the non-pipeable variant of select was called
it('should call selector selector1', () => {
expect(mockStore.select).toHaveBeenCalledWith(selector1);
});
// this is checking if the non-pipeable variant of select was called
it('should call selector selector2', () => {
expect(mockStore.select).toHaveBeenCalledWith(selector2);
});
});
});
I am a beginner with Angular tests. Feedback is appreciated.
I took the last version posted by @marcelnem original by @maxime1992 and did a poke with it in stackblitz
-> https://stackblitz.com/edit/ngrx-component-testing-simplified?file=src%2Fapp%2Fluffy%2Fluffy.component.spec.ts
I didn't check any line of the implementation but it tooks me 5sec to mock the selector using it! YEAHH.
Original @TeoTN proposal can also be added in // to this. One do not exclude the other but this is definitly needed to simplify the smart component testing related to ngrx.
@MikeRyanDev look at it please if it needs code refactoring, Let's proceed :)
because it ll make a lot of devs happy mocking selectors like that. And let's check if there is problem or limitation with current proposal.
I am personally suggesting for now the selectors mocking integration to the lib as high priority.
p.s. thank's to all of you for shaking this! i hope it will make it into the lib soon ^^
In my opinion @nasreddineskandrani聽solution is immediately ready for deprecation, as it's not following up to date RxJS standards for pipable operators. Moreover, it'd force people to use selector which is an opt-in helper, not obligatory part of the store.
The purpose of this is to let people test components given some fixed state without need to dispatch multiple actions. Mocking the select fn is just a poor workaround.
Think of how you test observables - you'd mock the data stream, not the operators
Moreover, it'd force people to use selector which is an opt-in helper, not obligatory part of the store.
Most of devs use select from @ngrx/store.
These devs want an efficient solution (provided also by the lib) to test around select operator.
For example to test the selectors as @timdeschryver explained in this post.
https://blog.angularindepth.com/how-i-test-my-ngrx-selectors-c50b1dc556bc
the ngrx lib provide Projector possibility (provided internally by ngrx lib).
===> The same thing apply for mocking the selectors.
The purpose of this is to let people test components given some fixed state without need to dispatch multiple actions. Mocking the select fn is just a poor workaround.
Nop! The purpose is to be able to write unit tests or integrated tests easily (Not only for components. Effects also).
@maxime1992 solution answer the needs ^^ and needs to be evaluated which i asked Mike for to see if the solution has limitation.
You evaluation of his solution is it's not following up to date RxJS standards for pipable operators.
Can you please provide example around this statement? to let us understand.
In my opinion @nasreddineskandrani solution is immediately ready for deprecation
you are writing comment, deleting them on this github issue... then closing the issue and reopening it.
You should try to understand others needs/problems :( and not push only for your proposal
@TeoTN if you want to mock the whole store, and do not want to use selectors. Can't you just use the real ngrx store and dispatch a NextMock action and have a NextMock reducer which replaces the whole state of the store? Is there a performance issue with that? From your first post it is not clear to me why a mock store is needed and a real store cannot be used. Or is there anything else that you expect from a MockStore apart from the nextMock function which you mention?
I see the benefit of mocking just selectors in that I do not have to construct the whole state (which is quite complicated in our application). I only need to mock the response of selectors. For me passing the whole state using nextMock is not always practical.
@marcelnem
I see the benefit of mocking just selectors in that I do not have to construct the whole state (which is quite complicated in our application). I only need to mock the response of selectors. For me passing the whole state using nextMock is not always practical.
exactly! thank you for highlighting this.
@nasreddineskandrani I've closed the issue for a way different reason and reopened it after a few minutes, I don't see reason to explain that. I'm not pushing, I think though that what happened here is that a few guys came over there and with barely glancing at the PR they decided to copy-paste their solutions they've used without a word of justification why those would be better. I don't care what worked for you, I do care about why my solution wouldn't work for you and how can I improve that. If you feel yours is better - open up another PR. I don't think that this kind of elbowing is productive nor fruitful.
Mocking selectors one by one doesn't take into account that a piece of an app may use multiple selectors and you'd have to take all the intertwined logic into account while mocking. Contrary to that, mocking the top-level state allows to pass a real snaphot of your environment (if something is missing, it's an error). And it actually needs _only parts of state_that are actually consumed within the testbed. It works like a charm in large, enterprise level apps.
As for pipable operators - lol, I won't comment on that. Do the homework and read why they were introduced. I'd recommend that you actually read source code of the lib you want to write PRs for. -> select deprecation.
@marcelnem no, as it eliminates possibility of doing some integration testing - fake reducers and fake actions don't give you the actual environment. + why would I write that for testing every app that's using ngrx?
Again - you don't test observables by mocking their operators, why would you do this frankenstein logic for the store?
Mocking selectors one by one doesn't take into account that a piece of an app may use multiple selectors and you'd have to take all the intertwined logic into account while mocking.
You can put a breakpoint grab the value at runtime in the last needed selector and use it as a mocked data. Should be fast without thinking about interwired logic.
As for pipable operators - lol, I won't comment on that. Do the homework and read why they were introduced. I'd recommend that you actually read source code of the lib you want to write PRs for. -> select deprecation.
I know about the deprecation of select and ofType.
I am using the pipable version of ngrx select in the example i built from @maxime1992 and @marcelnem posts => it's working fine.
https://stackblitz.com/edit/ngrx-component-testing-simplified?file=src%2Fapp%2Fluffy%2Fluffy.component.spec.ts
I investigated:
ngrx team deprecated the select inside the Store class.
https://github.com/ngrx/platform/blob/master/modules/store/src/store.ts#L23
in @maxime1992 code, he is using import * as ngrx from '@ngrx/store';
and spyOnProperty the select outside of the Store class, which means this one:
https://github.com/ngrx/platform/blob/master/modules/store/src/store.ts#L115
that's why it's working :)
please be kind in your answers because you never know who don't know ;) you or the others.
@nasreddineskandrani If you wanted me to treat you with respect, you should've started with reading my PR. You've admitted not having read the code and started off with something different. Do you even know what you're commenting?
I see at least three major issues with the select-mocking approach:
1. Purpose
As you consider promises, thunks, generators, observables and CSP, these are all some wrappers building around a data structure, that will be available in future, possibly changing over time, defered in retrieval, or cancellable, but all in all, the code units you write depend on the wrapped data structure (e.g. current value of the observable). Therefore, when testing the unit, you want to provide a fixture of that piece of data to abstract from any particular use case.
Mocking select is about mocking one particular use case of the surrounding data structure (in this case the observable), whereas mocking the state is about the actual underlaying data.
Please refer to the docs on selectors / advanced usage to see that it clearly states that you may want to use custom operators instead of select (they may be built on top of select but don't have to)
2. Approachability
Both the select-mocking and state-mocking approaches are equivalent in terms of ease of building a fixture. In both cases you're mocking only what's needed. For selectors, you're mocking the value returned by the given selector. For state mocking, you're building state snapshot from root's perspective but _only include what's needed_.
The drawback of select-mocking though is that when testing smart components (possibly in integration testing manner with nested smart components) you have to ensure _each and every_ selector is mocked or explicitly rely on the initial state. In state-mocking you're declarative about what's used.
3. Lightweight
You have imported the whole ngrx/store which is unacceptable in systems without tree shaking. State-mocking MockStore is depending only on a few parts.
Your implementation for non-observable values would create a brand new observable for every call to select, whereas the state-mocking approach does the thing once in construction time.
@TeoTN Piotr, I think that the solutions are complementary. I understand better now that if the logic in selectors is complicated it might be easier to make a snapshot of a store and inject the whole snapshot using the nextMock function.
I agree that the code that @maxime1992 and I have posted does not cover the scenario when somebody wants to use mockStore.pipe(take(1)) or similar ..., It only mocks the select function, the select pipeable operator, and spies on the dispatch function. It is not a complete mock that covers the whole API of the store.
For integration tests (components+selectors+store working together) the nextMock solution is handy. However, for isolated unit tests, if we only have the nextMock solution I would be missing the ability to mock the selectors. If a bug is introduced in a selector function we now might get errors in the component specs. I do not find this ideal when the goal is to have isolated unit tests for components.
By being able to mock the returned value of the selector as is possible in the solution of @maxime1992, the tests are isolated. They do not depend on the correctness of selectors.
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, so I think people like to use it. It also prevents having to keep and maintain/update snapshots of the whole store.
@marcelnem I have to start from the end:
It also prevents having to keep and maintain/update snapshots of the whole store.
There's no need to provide unused parts of the state, you are mocking from root's perspective but omit unused parts :) So you're not maintaining the whole-store snapshots. However, you may need to maintain the initial state but I guess this is something that everyone has, or may obtain with reducer(undefined, {})
I admit I have different approach to testing selectors, presumably not the best practice but I was fine with components failing on errorneous selectors. I consider them a part of the component that was extracted but I see your point too.
I am not sure though, why you'd need a mock for select operator, given that the selector function can be mocked itself - probably sth similar to:
import * as fromSelectors from './selectors';
...
spyOnProperty(fromSelectors, 'mySelector').and.returnValue(mockedData$);
Wouldn't this work? If import in the component file wouldn't take this into account (not sure how the spy method is working) you could do this same way as while testing on node - with proxy lib on imports.
You've admitted not having read the code and started off with something different. Do you even know what you're commenting?
true. I subscribed to this issue because i think ngrx lib need the mockStore => how you implement it :) doesn't matter at first place for me. I just retained from the description that provides nextMock method that replaces current state.
let's recap.
@marcelnem
For integration tests (components+selectors+store working together) the nextMock solution is handy.
true! I agree with this. That's why i am saying one solution don't exclude the other (nextMock & selectors mocks).
@marcelnem
I do not find this ideal when the goal is to have isolated unit tests for components.
@TeoTN
I consider them a part of the component that was extracted but I see your point too.
great! we want to isolate unittests. but @TeoTN don't like having the ability of mocking only select operator which is weak for devs that have custom selectors.
Like selectLastStateTransitions from the official doc.
(so the remaining problem is here)
UPDATE: no problem @TeoTN your suggestion works for the advanced selectors case! doing this 馃拑
import * as fromSelectors from './selectors';
...
spyOnProperty(fromSelectors, 'mySelector').and.returnValue(mockedData$);
So for all selectors, we just need to use the select operator only in selectors files to be able to mock easily. With that we don't need to add mocking select operator to ngrx mockStore. You all agree?
@nasreddineskandrani
can you post an example how you made it work with the spyOnProperty snippet please?
import * as fromSelectors from './selectors';
...
spyOnProperty(fromSelectors, 'mySelector').and.returnValue(mockedData$);
With that we don't need to add mocking select operator to ngrx mockStore. You all agree?
i think it's not true it moved the problem to the selectors side :) but it's evolving.
@marcelnem I am writing an example on stackblitz. :D give me some time
When I tried to use the snippet, I run into the problem that the selector functions would have to be defined as a property of some object. Discussed here: https://github.com/jasmine/jasmine/issues/1415. I did not get over it. Maybe it is easier to spy on property the ngrx/select and spy on Store.select() function as was doing the code from @maxime1992 .
facing the same issue. Checking it
UPDATE: it's workingggg!!!!
https://stackblitz.com/edit/ngrx-component-testing-simplified?file=src%2Fapp%2Fluffy%2Fluffy.component.spec.ts
//luffy.component.spec.ts
import * as luffySelectors from './luffy.selector';
...
spyOn(luffySelectors, 'selectSuperPowerFlying').and.returnValue(of(true));
...
//luffy.selector.ts
...
export const selectSuperPowerFlying = pipe(
select(getSuperPowerFlying)
);
...
This is just awesome ^^ for unit testing components and effects => testing will be faster now without waiting for update of ngrx 馃拑 ! thanks for all interactions :) we got something 馃憤
again i think we still need to be able to mock select operator :) since using this strategy solves the problem for unit testing components/effects and maybe moved the problem to the tests of selectors.
Also some devs use select directly in the components...
@MikeRyanDev @brandonroberts
Can we move forward with @TeoTN PR to have the mockStore and nextMock ability for integration tests as a first step?
and tackle the select operator mocking for unit tests in another PR as a second step.
Im really happy to see there are growing something which will awesome! I like both approaches. There are cases, which is good to mock the selectors and cases where to mock the store.
Thank you guys !
Update:
regardless of the fact that we have now come to a good solution (I hope), I have learned in this tread a lot about to mock the store/selector. This thread is really great. Thanks for that.
@nasreddineskandrani
Is it necessary to wrap every selector
export const getSuperPowerFlying = createSelector(selectSuperPower, (superPower) => {
return superPower.fly;
})
into a pipe?
export const selectSuperPowerFlying = pipe(
select(getSuperPowerFlying)
);
@marcelnem until they provide an official way to mock select. I suggest to use this and to be honest this is more powerful at first view. So yes I guess you have to. If someone has better he can provide his solution 馃槉 until then this is how am doing it from now.
The most important is to make the testing easier (I hope this become a priority soon for ngrx team since companies are suffering). With this we don.t need to trigger action to put the store in the right state to be able to unit tests. A huge time saved.
@marcelnem it is possible to create in the meantime a repo for your suggested approach so that we can work (improvements, tests, fixing bugs, etc) on it and when the approach from @TeoTN is already done (i guess in v7 of ngrx) then we can think about it to merge the mocking of selector into @TeoTN suggested approach. Why im saying that? I would like to able, to get the newest changes, bug fixes, etc.
I understand. I prefer to mock the selectors in some (most of the) situations (unit test of container components). Mocking the whole state or part of it is another tool which I also find useful in some cases (e.g. testing the selectors themselves), so the contribution of @TeoTN is valuable. But for isolated unit tests of containers, I think it is good to be able to mock the ngrx/select operator directly. By approving the PR without an ability to mock select, this might lead many people to mock the whole state in all cases just because this will be the only option in with the to-be-official MockStore.
Wrapping every selector into a pipe and adding a boilerplate code into each selectors.ts code just to be able to mock them is tedious. I find it better to mock the ngrx/store directly as in the code from @maxime1992.
@nasreddineskandrani hey sorry, see my last post! i mean you and not @marcelnem with it is possible to create in the meantime a repo .... :)
Can you create some repo?
I recommend to wait to see what ngrx team think about the current proposal(s). Before going and create a public repo in // to maintain a temporary MockStore out of the official lib. The right move is to get things done, approved and merged. Be patient!
yes you are right. I was to hasty!
@nasreddineskandrani This does not work for me:
spyOn(selectors, 'someSelector').and.returnValue('foo');
I get following error:
Error: <spyOn> : someSelector is not declared writable or has no setter
How did you do that it works for you? How can i make it writeable? my selectors are declared as const but changing let let same error.
@SerkanSipahi you need to build the selector with pipe(select... and add of( ) in the returnValue. Please check the online example i posted.
I checked the online example. I took an example which is not built with pipe (it works in online example):
luffy.selector.ts
export const selectPowerA = createSelector(selectLuffy, (luffyState) => {
return luffyState.powerA;
});
luffy.component.spec.js
spyOn(luffySelectors, 'selectPowerA').and.returnValue(of(true));
Error:
not error on your example
@SerkanSipahi GG! i assumed it's not working without pipe but it's even more simple 馃拑 so why are we trying to mock select :D.
With my friends, we didn't try this at first place following the doc with action triggers. But it was none productive that's why i was hunting a better solution.
So i guess an update is needed with this way of mocking selectors into the ngrx testing documentation. For the doc update:
Question: this kill the need of mocking select in mockStore or someone see a case?
I was really exited about that we getting mock store, selector, etc but i think its not ready for usage its makes more complicated. I dont know.
Well, mocking selector and store are not worked well for me or just lets say: really hard to do that simple (maybe im stupid) or maybe it is not fully developed yet, i dont know! For me a test setup should be very easy without or with less think about it.
For me it workes very well and easy to test my effect with the real store as @alex-okrushko suggested!
effect:
@Effect({ dispatch: false }) toggleAppElement$ = this.actions$.pipe(
ofType<Fullscreen>(FullscreenActionTypes.Event),
switchMap(_ => combineLatest(
this.store.pipe(select(fromRoot.getFullscreen)),
this.store.pipe(select(fromRoot.getAppElement), skip(1)),
this.video.getElement$<HTMLVideoElement>('video')),
),
filter(([, appEl]) => appEl instanceof Element),
tap(([fullscreen, appEl, videoEl]) =>
this.toggleAppElement(fullscreen, appEl, videoEl)
),
);
effect.spec:
describe('FullscreenEffects', () => {
let store$: Store<fromRoot.State>;
let fullscreenEffects: FullscreenEffects;
let videoService: VideoService;
let fakeEvent = (): Event => new Event('fakeName');
let fakeElement = (): Element => document.createElement('div');
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers }),
],
providers: [
Store,
FullscreenEffects,
Actions,
VideoService,
],
});
fullscreenEffects = TestBed.get(FullscreenEffects);
videoService = TestBed.get(VideoService);
store$ = TestBed.get(Store);
});
it('should call fullscreenEffects.toggleAppElement', done => {
spyOn(videoService, 'getElement$').and.returnValue(of(fakeElement()));
spyOn(fullscreenEffects, 'toggleAppElement');
fullscreenEffects.toggleAppElement$.subscribe(_ => {
expect(fullscreenEffects.toggleAppElement).toHaveBeenCalledTimes(1);
done();
});
store$.dispatch(new AppElement(fakeElement()));
store$.dispatch(new Fullscreen(fakeEvent()));
});
});
Now im not agree with @TeoTN of: It's probably a waste of time, programmers' keyboards and CPUs power!
what a waste of time? it was really easy! cpu? i have not 1000 effects thats run in tests!
with @TeoTN proposal you can set a state in the store without triggering actions. In your case, you are not triggering actions to set a state. But if you had to trigger actions @TeoTN proposal is better.
And then his comment would have applied t's probably a waste of time, programmers' keyboards and CPUs power about triggering actions.
with @TeoTN proposal you can set a state in the store without triggering actions
But i have to triggering a action. How should i catch ofType<Fullscreen>(FullscreenActionTypes.Event)
In your case, you are not triggering actions to set a state
See im triggering a action(s) and setting a new state:
store$.dispatch(new Fullscreen(fakeEvent()));
store$.dispatch(new AppElement(fakeElement()));
But if you had to trigger actions @TeoTN proposal is better
It did not worked for me. I can just set a state with nextMock and thats it. How to go through ofType<Fullscreen>(FullscreenActionTypes.Event)
Sorry i didn't see it :)
store$.dispatch(new AppElement(fakeElement())); not store$.dispatch(new Fullscreen(fakeEvent())); which fire the stream.select operator grab the data you want in the combineLatest by mocking selectors.2.
I already explained one case with common component where triggering actions isn't welcome
here -> https://github.com/ngrx/platform/issues/915#issuecomment-405089678
i ll give you another case related to your example,
lets say select(fromRoot.getAppElement) rely on a state that need 1 other action ALHPA to happen before to select proprely what you need. This action ALHPA trigger 4 backend calls. You need to go mock all these 4 api calls to get the store in the right state before triggering the effect.
Mocking the selector allow you to avoid the mock of the 4 apis calls.... by copy/pasting the content of your selector grabbed at runtime (debug mode). And dont worry about updating the mocking of api calls in this test if they change overtime when the end data type in the selector remain the same.
=> You test only the current unit.
*this is not common for me to see 'dom' stuff in effect ( ), you can poke me in gitter in private if you wanna talk about this or ask in gitter/ngrx. I built multi app with redux react/angular never had to do that.
@SerkanSipahi can you post the full example how you made it work with a selector which is not wrapped? I tried it in the example and I get an error. https://stackblitz.com/edit/ngrx-component-testing-simplified-7fd5nk?file=src/app/luffy/luffy.component.spec.ts
@marcelnem i tried it it's not working for me without pipe i can't spend more time on this. Looking forward to see what ngrx team decide after all these interactions.
@marcelnem @nasreddineskandrani I think here is a misunderstanding! What i meant was, its not possible to compile that thing when its not wrapped by pipe!
I add spyOn(luffySelectors, 'selectPowerB').and.returnValue(of(true)); into your beforeEach example inside of component fast testing with selectors mocking and i get no compile error like on my env: Error: <spyOn> : getFullscreen is not declared writable or has no setter
When i do that with my selectors to spyOn a selector which is not wrapped by pipe i get an error.
Which version of ngrx you are using?
1.) ... you need to trigger the effect. This is the action you can avoid store$.dispatch(new AppElement(fakeElement())); not store$.dispatch(new Fullscreen(fakeEvent())); which fire the stream....
2.) ... i ll give you another case related to your example,
lets say select(fromRoot.getAppElement) rely on a state that need 1 other ac
@nasreddineskandrani thank you for your good explanation! Now i got it. It took a while until I understood it (it was to much: mock.selector, mock.next, marble tests, etc. etc)
For just clarification(to improve my understanding): in my case it make no sense to mock the store (nextMock), right? Or is there something what i not see? maybe, imagine select(fromRoot.getAppElement) use internally other selectors to bulid the state for fromRoot.getAppElement. As far as I understand it, i have to mock then each selector, one by one but with store.nextMock i could just set the state easily.
Update: I left some feedback on the PR for the proposed testing API for Store here https://github.com/ngrx/platform/pull/1027#issuecomment-421063561
Leave feedback if you have any here and not there.
@brandonroberts
I totally agree with you testing component this way will work and it's better than trigger actions :+1:
so setState solve the original main issue
some argumentations on why, in my opinion, mocking selectors could be good also!
We shouldn't focus on mocking selectors. If you want to test selectors, provide a state that the selectors can use or use StoreModule.forRoot() with reducers for integration.
From your statement, i wanted to say that in my case i don't want to test selectors when i want to unit test components. why? Because integration tests are slower and should be fewer than unit tests to fail faster.
1.
Also there is a way the projector in selector built-in in ngrx to unit tests selectors
->to be consitent i saw a unit test ability in the lib for components as a nice to have.
https://blog.angularindepth.com/how-i-test-my-ngrx-selectors-c50b1dc556bc
conclusion section of article:
A selector can be unit tested, you don鈥檛 need a (mocked) store in order to test your selectors.
2.
By your statement you don't provide a way to unit test components (doc update or code update) and encourage integration tests with real selectors to test components.
I am insisting again about the fact it's slower and it may be a problem for some.
3.
i know :) i am taking it far but another little point, when it fails you know it's in the component not the selector => More isolated.
thank you for the answer and i am happy setState is going in to stop the need of triggering actions to be able to test a smart component
@nasreddineskandrani I see what you are saying. If you want to unit test a component that has selectors, override the class variables with test observables. Here is a simple example of overriding the selector itself, versus setting the state and using the selector as-is.
https://stackblitz.com/edit/ngrx-test-template?file=app%2Fcounter%2Fcounter.component.spec.ts
thank you very much so simple 馃憤
now that you mention it here devs will see it
maybe a little section in the doc about what you just posted.
You know more than me :) if it deserve a place in the doc. I am not that bad in habit but honestly i didn't see this solution. thanks again
@maxime1992 responding to your comment in the PR - https://github.com/ngrx/platform/pull/1027#issuecomment-421550271.
While your solution works, it's bound to jasmine, which is a downside in my opinion.
If you want to mock the selectors, see @brandonroberts 's answer https://github.com/ngrx/platform/issues/915#issuecomment-421410873.
@timdeschryver that's a fair point.
That said, I don't really like the idea of replacing directly the component's variable:
of is probably inappropriate because it'll close the stream after the first emit. So if you are triggering side effects when the stream closes, it might be way too early. Of course, we could create a new Observable, but it's a bit less straightforward. Those are valid points :)
@maxime1992 I don't disagree with those points, but I don't think we should use a Jasmine/Jest specific way to mock selectors. Using of is not the only way of doing it of course if you need to push multiple values.
I haven't tested this, but selectors could include an API you can access on the selector itself similar to release but it would short circuit the return value.
Here is a newer version that lets you override selectors in addition to state. It adds a method to the returned selector to let you set the result value that will be returned.
import { Injectable, Inject, OnDestroy } from '@angular/core';
import {
Store,
ReducerManager,
ActionsSubject,
ActionReducer,
Action,
ScannedActionsSubject,
INITIAL_STATE,
MemoizedSelector,
MemoizedSelectorWithProps,
} 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> {
static selectors = new Set<
MemoizedSelector<any, any> | MemoizedSelectorWithProps<any, any, any>
>();
constructor(
private state$: MockState<T>,
actionsObserver: ActionsSubject,
reducerManager: ReducerManager,
public scannedActions$: ScannedActionsSubject,
@Inject(INITIAL_STATE) private initialState: T
) {
super(state$, actionsObserver, reducerManager);
this.resetSelectors();
this.state$.next(this.initialState);
}
setState(state: T): void {
this.state$.next(state);
}
overrideSelector<T, Result>(
selector: MemoizedSelector<T, Result>,
value: Result
): MemoizedSelector<T, Result>;
overrideSelector<T, Result>(
selector: MemoizedSelectorWithProps<T, any, Result>,
value: Result
): MemoizedSelectorWithProps<T, any, Result>;
overrideSelector<T, Result>(
selector:
| MemoizedSelector<any, any>
| MemoizedSelectorWithProps<any, any, any>,
value: any
) {
MockStore.selectors.add(selector);
selector.setResult(value);
return selector;
}
resetSelectors() {
MockStore.selectors.forEach(selector => selector.setResult());
MockStore.selectors.clear();
}
dispatch(action: Action) {
super.dispatch(action);
this.scannedActions$.next(action);
}
addReducer() {
// noop
}
removeReducer() {
// noop
}
}
export function provideMockStore<T>(config: { initialState?: T } = {}) {
return [
{ provide: INITIAL_STATE, useValue: config.initialState },
ActionsSubject,
ScannedActionsSubject,
MockState,
{ provide: ReducerManager, useClass: MockReducerManager },
{
provide: Store,
useClass: MockStore,
},
];
}
If we take the auth-guard.service.spec.ts from the example app, the test now looks like this.
import { TestBed, inject } from '@angular/core/testing';
import { Store, MemoizedSelector } from '@ngrx/store';
import { cold } from 'jasmine-marbles';
import { AuthGuard } from '@example-app/auth/services/auth-guard.service';
import { AuthApiActions } from '@example-app/auth/actions';
import * as fromRoot from '@example-app/reducers';
import * as fromAuth from '@example-app/auth/reducers';
import { provideMockStore, MockStore } from '@ngrx/store/testing';
describe('Auth Guard', () => {
let guard: AuthGuard;
let store: MockStore<any>;
let loggedIn: MemoizedSelector<fromAuth.State, boolean>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideMockStore()],
});
store = TestBed.get(Store);
guard = TestBed.get(AuthGuard);
loggedIn = store.overrideSelector(fromAuth.getLoggedIn, false);
});
it('should return false if the user state is not logged in', () => {
const expected = cold('(a|)', { a: false });
expect(guard.canActivate()).toBeObservable(expected);
});
it('should return true if the user state is logged in', () => {
loggedIn.setResult(true);
const expected = cold('(a|)', { a: true });
expect(guard.canActivate()).toBeObservable(expected);
});
});
@brandonroberts getting: selector.setResult is not a function. I am using Jest instead of Jasmine but I don't think that matters in here.
@Bielik20 this code has not been merged into master yet. @TeoTN how do you want to proceed with this PR?
@brandonroberts I like some of the ideas from here and the PR. I was busy recently and didn't have time to incorporate them in the code, but I'll definitely take the time to improve that. Stay tuned ;)
@TeoTN
I can pr in your fork if you are short in time and you need help (especially for the tests/doc update).
I wrote you in your personal gitter, let me know over there.
@brandonroberts
I checked the 7.0.0-beta.1 version, and i do not see Store.overrideSelector method which you presented few comments above.
I see only such class:
export declare class MockStore<T> extends Store<T> {
private state$;
private initialState;
scannedActions$: Observable<Action>;
constructor(state$: MockState<T>, actionsObserver: ActionsSubject, reducerManager: ReducerManager, initialState: T);
setState(nextState: T): void;
addReducer(): void;
removeReducer(): void;
}
Shouldn't be overrideSelector method merged?
@tomasznastaly
it's not in. They need time
please read here brandon comment: https://github.com/ngrx/platform/pull/1027#issuecomment-433054427
Most helpful comment
I'd appreciate a complete testing guide. Testing every part of the store (including router state) AND inclusion in components throught selectors. Testing documention is almost non existent.