Skip to content

Commit 070228c

Browse files
John Crowsonbrandonroberts
authored andcommitted
feat(store): add option to mock selectors in MockStoreConfig (#1836)
Closes #1827
1 parent a8ce110 commit 070228c

File tree

8 files changed

+93
-17
lines changed

8 files changed

+93
-17
lines changed

modules/store/spec/store.spec.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,11 +449,24 @@ describe('ngRx Store', () => {
449449

450450
describe('Mock Store', () => {
451451
let mockStore: MockStore<TestAppSchema>;
452-
const initialState = { counter1: 0, counter2: 1 };
452+
const initialState = { counter1: 0, counter2: 1, counter4: 3 };
453+
const stringSelector = 'counter4';
454+
const memoizedSelector = createSelector(
455+
() => initialState,
456+
state => state.counter4
457+
);
453458

454459
beforeEach(() => {
455460
TestBed.configureTestingModule({
456-
providers: [provideMockStore({ initialState })],
461+
providers: [
462+
provideMockStore({
463+
initialState,
464+
selectors: [
465+
{ selector: stringSelector, value: 87 },
466+
{ selector: memoizedSelector, value: 98 },
467+
],
468+
}),
469+
],
457470
});
458471

459472
mockStore = TestBed.get(Store);
@@ -483,7 +496,31 @@ describe('ngRx Store', () => {
483496
mockStore.dispatch(action);
484497
});
485498

486-
it('should allow mocking of store.select with string selector', () => {
499+
it('should allow mocking of store.select with string selector using provideMockStore', () => {
500+
const expectedValue = 87;
501+
502+
mockStore
503+
.select(stringSelector)
504+
.subscribe(result => expect(result).toBe(expectedValue));
505+
});
506+
507+
it('should allow mocking of store.select with a memoized selector using provideMockStore', () => {
508+
const expectedValue = 98;
509+
510+
mockStore
511+
.select(memoizedSelector)
512+
.subscribe(result => expect(result).toBe(expectedValue));
513+
});
514+
515+
it('should allow mocking of store.pipe(select()) with a memoized selector using provideMockStore', () => {
516+
const expectedValue = 98;
517+
518+
mockStore
519+
.pipe(select(memoizedSelector))
520+
.subscribe(result => expect(result).toBe(expectedValue));
521+
});
522+
523+
it('should allow mocking of store.select with string selector using overrideSelector', () => {
487524
const mockValue = 5;
488525

489526
mockStore.overrideSelector('counter1', mockValue);
@@ -493,7 +530,7 @@ describe('ngRx Store', () => {
493530
.subscribe(result => expect(result).toBe(mockValue));
494531
});
495532

496-
it('should allow mocking of store.select with a memoized selector', () => {
533+
it('should allow mocking of store.select with a memoized selector using overrideSelector', () => {
497534
const mockValue = 5;
498535
const selector = createSelector(
499536
() => initialState,
@@ -507,7 +544,7 @@ describe('ngRx Store', () => {
507544
.subscribe(result => expect(result).toBe(mockValue));
508545
});
509546

510-
it('should allow mocking of store.pipe(select()) with a memoized selector', () => {
547+
it('should allow mocking of store.pipe(select()) with a memoized selector using overrideSelector', () => {
511548
const mockValue = 5;
512549
const selector = createSelector(
513550
() => initialState,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { MemoizedSelector, MemoizedSelectorWithProps } from '@ngrx/store';
2+
3+
export interface MockSelector {
4+
selector:
5+
| string
6+
| MemoizedSelector<any, any>
7+
| MemoizedSelectorWithProps<any, any, any>;
8+
value: any;
9+
}

modules/store/testing/src/mock_store.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
MemoizedSelector,
1212
} from '@ngrx/store';
1313
import { MockState } from './mock_state';
14+
import { MockSelector } from './mock_selector';
15+
import { MOCK_SELECTORS } from './tokens';
1416

1517
@Injectable()
1618
export class MockStore<T> extends Store<T> {
@@ -27,12 +29,23 @@ export class MockStore<T> extends Store<T> {
2729
private state$: MockState<T>,
2830
actionsObserver: ActionsSubject,
2931
reducerManager: ReducerManager,
30-
@Inject(INITIAL_STATE) private initialState: T
32+
@Inject(INITIAL_STATE) private initialState: T,
33+
@Inject(MOCK_SELECTORS) mockSelectors?: MockSelector[]
3134
) {
3235
super(state$, actionsObserver, reducerManager);
3336
this.resetSelectors();
3437
this.state$.next(this.initialState);
3538
this.scannedActions$ = actionsObserver.asObservable();
39+
if (mockSelectors) {
40+
mockSelectors.forEach(mockSelector => {
41+
const selector = mockSelector.selector;
42+
if (typeof selector === 'string') {
43+
this.overrideSelector(selector, mockSelector.value);
44+
} else {
45+
this.overrideSelector(selector, mockSelector.value);
46+
}
47+
});
48+
}
3649
}
3750

3851
setState(nextState: T): void {

modules/store/testing/src/testing.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import {
99
} from '@ngrx/store';
1010
import { MockStore } from './mock_store';
1111
import { MockReducerManager } from './mock_reducer_manager';
12+
import { MockSelector } from './mock_selector';
13+
import { MOCK_SELECTORS } from './tokens';
1214

1315
export interface MockStoreConfig<T> {
1416
initialState?: T;
17+
selectors?: MockSelector[];
1518
}
1619

1720
export function provideMockStore<T = any>(
@@ -21,6 +24,7 @@ export function provideMockStore<T = any>(
2124
ActionsSubject,
2225
MockState,
2326
{ provide: INITIAL_STATE, useValue: config.initialState },
27+
{ provide: MOCK_SELECTORS, useValue: config.selectors },
2428
{ provide: StateObservable, useClass: MockState },
2529
{ provide: ReducerManager, useClass: MockReducerManager },
2630
{ provide: Store, useClass: MockStore },
@@ -30,3 +34,4 @@ export function provideMockStore<T = any>(
3034
export { MockReducerManager } from './mock_reducer_manager';
3135
export { MockState } from './mock_state';
3236
export { MockStore } from './mock_store';
37+
export { MockSelector } from './mock_selector';

modules/store/testing/src/tokens.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { InjectionToken } from '@angular/core';
2+
3+
export const MOCK_SELECTORS = new InjectionToken('@ngrx/store Mock Selectors');

projects/example-app/src/app/auth/containers/login-page.component.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ describe('Login Page', () => {
2323
ReactiveFormsModule,
2424
],
2525
declarations: [LoginPageComponent, LoginFormComponent],
26-
providers: [provideMockStore()],
26+
providers: [
27+
provideMockStore({
28+
selectors: [{ selector: fromAuth.getLoginPagePending, value: false }],
29+
}),
30+
],
2731
});
2832

2933
fixture = TestBed.createComponent(LoginPageComponent);
3034
instance = fixture.componentInstance;
3135
store = TestBed.get(Store);
32-
store.overrideSelector(fromAuth.getLoginPagePending, false);
3336

3437
spyOn(store, 'dispatch');
3538
});

projects/example-app/src/app/books/containers/collection-page.component.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ describe('Collection Page', () => {
3434
AddCommasPipe,
3535
EllipsisPipe,
3636
],
37-
providers: [provideMockStore()],
37+
providers: [
38+
provideMockStore({
39+
selectors: [{ selector: fromBooks.getBookCollection, value: [] }],
40+
}),
41+
],
3842
});
3943

4044
fixture = TestBed.createComponent(CollectionPageComponent);
@@ -45,8 +49,6 @@ describe('Collection Page', () => {
4549
});
4650

4751
it('should compile', () => {
48-
store.overrideSelector(fromBooks.getBookCollection, []);
49-
5052
fixture.detectChanges();
5153

5254
expect(fixture).toMatchSnapshot();

projects/example-app/src/app/books/containers/find-book-page.component.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,23 @@ describe('Find Book Page', () => {
4444
AddCommasPipe,
4545
EllipsisPipe,
4646
],
47-
providers: [provideMockStore()],
47+
providers: [
48+
provideMockStore({
49+
selectors: [
50+
{ selector: fromBooks.getSearchQuery, value: '' },
51+
{ selector: fromBooks.getSearchResults, value: [] },
52+
{ selector: fromBooks.getSearchLoading, value: false },
53+
{ selector: fromBooks.getSearchError, value: '' },
54+
],
55+
}),
56+
],
4857
});
4958

5059
fixture = TestBed.createComponent(FindBookPageComponent);
5160
instance = fixture.componentInstance;
5261
store = TestBed.get(Store);
5362

5463
spyOn(store, 'dispatch');
55-
56-
store.overrideSelector(fromBooks.getSearchQuery, '');
57-
store.overrideSelector(fromBooks.getSearchResults, []);
58-
store.overrideSelector(fromBooks.getSearchLoading, false);
59-
store.overrideSelector(fromBooks.getSearchError, '');
6064
});
6165

6266
it('should compile', () => {

0 commit comments

Comments
 (0)