Currently, we can use store.overrideSelector(selector, value) and selector.setResult(value) to emit arbitrary values for a selector, which is great.
However, it would be awesome if we could also _provide an observable instead_ of calling setResult() each time we need to emit a new value.
It could be used like this: store.overrideSelectorWithObservable(selector, obs).
This is a much more generic approach, similar to how we override the actions dispatched in the store. provideMockActions(() => actions) is generic enough: we can use Subjects and any other implementations of Observables.
This also plays perfectly with other testing libraries, for example rxjs-marbles: actions = m.cold('--a', {a: someAction()}). It would be great to be able to override a selector in a similar way: store.overrideSelectorWithObservable(selector, storeValues); storeValues = m.cold('--1', {'1': 'someValue'})
_Note: There might easily be a better name than overrideSelectorWithObservable :) It could probably even be just an overload of overrideSelector_
Currently I simply have to refrain from using rxjs-marbles for testing functionality that depends on selectors, which is very annoying.
There might also be a way to create some kind of a helper that would listen to a source observable and, whenever any value is emitted, call selector.setResult(value); store.refreshState();. However, this code would then be reused in many places, and, IMO, this possibility should be provided by the ngrx itself.
[x] Yes (Assistance is provided if you need help submitting a pull request)
[ ] No
Hey @Maximaximum. I've actually spent a good chunk of time looking into what you are asking for (I opened https://github.com/ngrx/platform/issues/2329 for that), but it ends being a very intrusive change.
Hi @alex-okrushko! Not sure if #2329 is actually about the same thing.
As far as I understand, your issue was about being able to push new values to an overriden selector. And it already works by using store.overrideSelector(selector, value); and then selector.setResult(newValue); store.refreshState(), so you closed that issue.
My issue, however, is about being able to use a specific pattern to do this: store.overrideSelectorWithObservable(selector, obs) and then obs.next(newValue).
If I am mistaken and we're actually talking about the same thing, then may I ask you what do you mean by an intrusive change? Will it inevitably add a breaking change? Or is it just too complex to implement?
I've created a helper function to workaround this issue:
import { MemoizedSelector, MemoizedSelectorWithProps } from '@ngrx/store';
import { MockStore } from '@ngrx/store/testing';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
export function mockSelector<TStoreState, TValue>(
store: MockStore<TStoreState>,
selector: MemoizedSelector<TStoreState, TValue> | MemoizedSelectorWithProps<TStoreState, any, TValue>,
initialValue: TValue,
selectorValues: Observable<TValue>
) {
store.overrideSelector(selector, initialValue);
return selectorValues.pipe(
tap(v => {
selector.setResult(v);
store.refreshState();
})
).subscribe();
}
Which has to be used like this:
describe('some ngrx effect', () => {
let fooSelectorValues: Subject<FooSelectorValue>;
beforeEach(() => {
fooSelectorValues = new Subject();
mockSelector(store, fooSelector, someInitialValue, fooSelectorValues);
});
it('should react somehow on state changes', () => {
fooSelectorValues.next(newValue);
// ... some expectations here ...
});
});
However, it's very far from being perfect:
store.select(selector). And there is no way to delay the initial value emission. This is quite inconvenient IMHO.selectorValues observable can only contain updates to the selector values, but not the first value to be emitted.Closing this as we're going to keep it as is for now