Skip to content

Commit

Permalink
feat(store): add testing package (#1027)
Browse files Browse the repository at this point in the history
Introduces `@ngrx/store/testing` that provides a mock store for using in test setup

Closes #915 

```ts
import { Store } from '@ngrx/store';
import { provideMockStore, MockStore } from '@ngrx/store/testing';
import { take } from 'rxjs/operators';

describe('Mock Store', () => {
  let mockStore: MockStore<{ counter1: number, counter2: number }>;

  beforeEach(() => {
    const initialState = { counter1: 0, counter2: 1 };

    TestBed.configureTestingModule({
      providers: [provideMockStore({ initialState })],
    });

    mockStore = TestBed.get(Store);
  });

  it('should set the new state', () => {
    mockStore.setState({ counter1: 1, counter2: 2 });

    mockStore.pipe(take(1)).subscribe(state => {
      expect(state.counter1).toBe(1);
    });
  });
});
```
  • Loading branch information
TeoTN authored and brandonroberts committed Oct 25, 2018
1 parent d9de463 commit ab56aac
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 1 deletion.
2 changes: 2 additions & 0 deletions modules/store/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ ng_package(
name = "npm_package",
srcs = glob(["**/*.externs.js"]) + [
"package.json",
"//modules/store/testing:package.json",
],
entry_point = "modules/store/index.js",
packages = [
Expand All @@ -25,5 +26,6 @@ ng_package(
],
deps = [
":store",
"//modules/store/testing",
],
)
1 change: 1 addition & 0 deletions modules/store/spec/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ts_test_library(
),
deps = [
"//modules/store",
"//modules/store/testing",
"@rxjs",
"@rxjs//operators",
],
Expand Down
41 changes: 40 additions & 1 deletion modules/store/spec/store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
} from './fixtures/counter';
import Spy = jasmine.Spy;
import any = jasmine.any;
import { take } from 'rxjs/operators';
import { skip, take } from 'rxjs/operators';
import { MockStore, provideMockStore } from '../testing';

interface TestAppSchema {
counter1: number;
Expand Down Expand Up @@ -432,4 +433,42 @@ describe('ngRx Store', () => {
};
}
});

describe('Mock Store', () => {
let mockStore: MockStore<TestAppSchema>;

beforeEach(() => {
const initialState = { counter1: 0, counter2: 1 };

TestBed.configureTestingModule({
providers: [provideMockStore({ initialState })],
});

mockStore = TestBed.get(Store);
});

it('should set the initial state to a mocked one', (done: DoneFn) => {
const fixedState = {
counter1: 17,
counter2: 11,
counter3: 25,
};
mockStore.setState(fixedState);
mockStore.pipe(take(1)).subscribe({
next(val) {
expect(val).toEqual(fixedState);
},
error: done.fail,
complete: done,
});
});

it('should allow tracing dispatched actions', () => {
const action = { type: INCREMENT };
mockStore.scannedActions$
.pipe(skip(1))
.subscribe(scannedAction => expect(scannedAction).toEqual(action));
mockStore.dispatch(action);
});
});
});
19 changes: 19 additions & 0 deletions modules/store/testing/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package(default_visibility = ["//visibility:public"])

exports_files(["package.json"])

load("//tools:defaults.bzl", "ng_module")

ng_module(
name = "testing",
srcs = glob([
"*.ts",
"src/**/*.ts",
]),
module_name = "@ngrx/store/testing",
visibility = ["//visibility:public"],
deps = [
"//modules/store",
"@rxjs",
],
)
1 change: 1 addition & 0 deletions modules/store/testing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/testing';
11 changes: 11 additions & 0 deletions modules/store/testing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@ngrx/store/testing",
"typings": "./testing.d.ts",
"main": "../bundles/core-testing.umd.js",
"module": "../fesm5/testing.js",
"es2015": "../fesm2015/testing.js",
"esm5": "../esm5/testing/testing.js",
"esm2015": "../esm2015/testing/testing.js",
"fesm5": "../fesm5/testing.js",
"fesm2015": "../fesm2015/testing.js"
}
12 changes: 12 additions & 0 deletions modules/store/testing/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default {
entry: './dist/store/@ngrx/store/testing.es5.js',
dest: './dist/store/bundles/store-testing.umd.js',
format: 'umd',
exports: 'named',
moduleName: 'ngrx.store.testing',
globals: {
'@angular/core': 'ng.core',
'@ngrx/store': 'ngrx.store',
'rxjs': 'Rx',
}
}
12 changes: 12 additions & 0 deletions modules/store/testing/src/mock_reducer_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { ActionReducer } from '@ngrx/store';

@Injectable()
export class MockReducerManager extends BehaviorSubject<
ActionReducer<any, any>
> {
constructor() {
super(() => undefined);
}
}
9 changes: 9 additions & 0 deletions modules/store/testing/src/mock_state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable()
export class MockState<T extends {}> extends BehaviorSubject<T> {
constructor() {
super(<T>{});
}
}
38 changes: 38 additions & 0 deletions modules/store/testing/src/mock_store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Inject, Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import {
Action,
ActionsSubject,
INITIAL_STATE,
ReducerManager,
Store,
} from '@ngrx/store';
import { MockState } from './mock_state';

@Injectable()
export class MockStore<T> extends Store<T> {
public scannedActions$: Observable<Action>;

constructor(
private state$: MockState<T>,
actionsObserver: ActionsSubject,
reducerManager: ReducerManager,
@Inject(INITIAL_STATE) private initialState: T
) {
super(state$, actionsObserver, reducerManager);
this.state$.next(this.initialState);
this.scannedActions$ = actionsObserver.asObservable();
}

setState(nextState: T): void {
this.state$.next(nextState);
}

addReducer() {
/* noop */
}

removeReducer() {
/* noop */
}
}
32 changes: 32 additions & 0 deletions modules/store/testing/src/testing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Provider } from '@angular/core';
import { MockState } from './mock_state';
import {
ActionsSubject,
INITIAL_STATE,
ReducerManager,
StateObservable,
Store,
} from '@ngrx/store';
import { MockStore } from './mock_store';
import { MockReducerManager } from './mock_reducer_manager';

export interface MockStoreConfig<T> {
initialState?: T;
}

export function provideMockStore<T = any>(
config: MockStoreConfig<T> = {}
): Provider[] {
return [
ActionsSubject,
MockState,
{ provide: INITIAL_STATE, useValue: config.initialState },
{ provide: StateObservable, useClass: MockState },
{ provide: ReducerManager, useClass: MockReducerManager },
{ provide: Store, useClass: MockStore },
];
}

export { MockReducerManager } from './mock_reducer_manager';
export { MockState } from './mock_state';
export { MockStore } from './mock_store';
13 changes: 13 additions & 0 deletions modules/store/testing/tsconfig-build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../tsconfig-build",
"compilerOptions": {
"paths": {
"@ngrx/store": ["../../dist/packages/store"]
}
},
"files": ["index.ts"],
"angularCompilerOptions": {
// Work around for issue: https://github.com/angular/angular/issues/22210
"strictMetadataEmit": false
}
}

0 comments on commit ab56aac

Please sign in to comment.