# Mocking @ngrx/Store
# Unit Test For Component With Mock Store
This is a unit test of a component that has Store as a dependency. Here, we are creating a new class called MockStore that is injected into our component instead of the usual Store.
import { Injectable } from '@angular/core';
import { TestBed, async} from '@angular/core/testing';
import { AppComponent } from './app.component';
import {DumbComponentComponent} from "./dumb-component/dumb-component.component";
import {SmartComponentComponent} from "./smart-component/smart-component.component";
import {mainReducer} from "./state-management/reducers/main-reducer";
import { StoreModule } from "@ngrx/store";
import { Store } from "@ngrx/store";
import {Observable} from "rxjs";
class MockStore {
public dispatch(obj) {
console.log('dispatching from the mock store!')
}
public select(obj) {
console.log('selecting from the mock store!');
return Observable.of({})
}
}
describe('AppComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent,
SmartComponentComponent,
DumbComponentComponent,
],
imports: [
StoreModule.provideStore({mainReducer})
],
providers: [
{provide: Store, useClass: MockStore}
]
});
});
it('should create the app', async(() => {
let fixture = TestBed.createComponent(AppComponent);
let app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
# Observer Mock
class ObserverMock implements Observer<any> {
closed?: boolean = false; // inherited from Observer
nextVal: any = ''; // variable I made up
constructor() {}
next = (value: any): void => { this.nextVal = value; };
error = (err: any): void => { console.error(err); };
complete = (): void => { this.closed = true; }
}
let actionReducer$: ObserverMock = new ObserverMock();
let action$: ObserverMock = new ObserverMock();
let obs$: Observable<any> = new Observable<any>();
class StoreMock extends Store<any> {
constructor() {
super(action$, actionReducer$, obs$);
}
}
describe('Component:Typeahead', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [...],
declarations: [Typeahead],
providers: [
{provide: Store, useClass: StoreMock} // NOTICE useClass instead of useValue
]
}).compileComponents();
});
});
# Angular 2 - Mock Observable ( service + component )
service
- I created post service with postRequest method.
import {Injectable} from '@angular/core';
import {Http, Headers, Response} from "@angular/http";
import {PostModel} from "./PostModel";
import 'rxjs/add/operator/map';
import {Observable} from "rxjs";
@Injectable()
export class PostService {
constructor(private _http: Http) {
}
postRequest(postModel: PostModel) : Observable<Response> {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this._http.post("/postUrl", postModel, {headers})
.map(res => res.json());
}
}
Component
- I created component with result parameter and postExample function that call to postService.
- when the post resquest successed than result parameter should be 'Success' else 'Fail'
import {Component} from '@angular/core';
import {PostService} from "./PostService";
import {PostModel} from "./PostModel";
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.scss'],
providers : [PostService]
})
export class PostComponent{
constructor(private _postService : PostService) {
let postModel = new PostModel();
result : string = null;
postExample(){
this._postService.postRequest(this.postModel)
.subscribe(
() => {
this.result = 'Success';
},
err => this.result = 'Fail'
)
}
}
test service
- when you want to test service that using http you should use mockBackend. and inject it to it.
- you need also to inject postService.
describe('Test PostService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
PostService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}
}
]
});
});
it('sendPostRequest function return Observable', inject([PostService, MockBackend], (service: PostService, mockBackend: MockBackend) => {
let mockPostModel = PostModel();
mockBackend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.method).toEqual(RequestMethod.Post);
expect(connection.request.url.indexOf('postUrl')).not.toEqual(-1);
expect(connection.request.headers.get('Content-Type')).toEqual('application/json');
});
service
.postRequest(PostModel)
.subscribe((response) => {
expect(response).toBeDefined();
});
}));
});
test component
describe('testing post component', () => {
let component: PostComponent;
let fixture: ComponentFixture<postComponent>;
let mockRouter = {
navigate: jasmine.createSpy('navigate')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PostComponent],
imports: [RouterTestingModule.withRoutes([]),ModalModule.forRoot() ],
providers: [PostService ,MockBackend,BaseRequestOptions,
{provide: Http, deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}
},
{provide: Router, useValue: mockRouter}
],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('test postRequest success', inject([PostService, MockBackend], (service: PostService, mockBackend: MockBackend) => {
fixturePostComponent = TestBed.createComponent(PostComponent);
componentPostComponent = fixturePostComponent.componentInstance;
fixturePostComponent.detectChanges();
component.postExample();
let postModel = new PostModel();
let response = {
'message' : 'message',
'ok' : true
};
mockBackend.connections.subscribe((connection: MockConnection) => {
postComponent.result = 'Success'
connection.mockRespond(new Response(
new ResponseOptions({
body: response
})
))
});
service.postRequest(postModel)
.subscribe((data) => {
expect(component.result).toBeDefined();
expect(PostComponent.result).toEqual('Success');
expect(data).toEqual(response);
});
}));
});
# Unit Test For Component Spying On Store
This is a unit test of a component that has Store as a dependency. Here, we are able to use a store with the default "initial state" while preventing it from actually dispatching actions when store.dispatch() is called.
import {TestBed, async} from '@angular/core/testing';
import {AppComponent} from './app.component';
import {DumbComponentComponent} from "./dumb-component/dumb-component.component";
import {SmartComponentComponent} from "./smart-component/smart-component.component";
import {mainReducer} from "./state-management/reducers/main-reducer";
import {StoreModule} from "@ngrx/store";
import {Store} from "@ngrx/store";
import {Observable} from "rxjs";
describe('AppComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent,
SmartComponentComponent,
DumbComponentComponent,
],
imports: [
StoreModule.provideStore({mainReducer})
]
});
});
it('should create the app', async(() => {
let fixture = TestBed.createComponent(AppComponent);
let app = fixture.debugElement.componentInstance;
var mockStore = fixture.debugElement.injector.get(Store);
var storeSpy = spyOn(mockStore, 'dispatch').and.callFake(function () {
console.log('dispatching from the spy!');
});
}));
});
# Simple Store
simple.action.ts
import { Action } from '@ngrx/store';
export enum simpleActionTpye {
add = "simpleAction_Add",
add_Success = "simpleAction_Add_Success"
}
export class simpleAction {
type: simpleActionTpye
constructor(public payload: number) { }
}
simple.efficts.ts
import { Effect, Actions } from '@ngrx/effects';
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Observable } from 'rxjs';
import { simpleAction, simpleActionTpye } from './simple.action';
@Injectable()
export class simpleEffects {
@Effect()
addAction$: Observable<simpleAction> = this.actions$
.ofType(simpleActionTpye.add)
.switchMap((action: simpleAction) => {
console.log(action);
return Observable.of({ type: simpleActionTpye.add_Success, payload: action.payload })
// if you have an api use this code
// return this.http.post(url).catch().map(res=>{ type: simpleAction.add_Success, payload:res})
});
constructor(private actions$: Actions) { }
}
simple.reducer.ts
import { Action, ActionReducer } from '@ngrx/store';
import { simpleAction, simpleActionTpye } from './simple.action';
export const simpleReducer: ActionReducer<number> = (state: number = 0, action: simpleAction): number => {
switch (action.type) {
case simpleActionTpye.add_Success:
console.log(action);
return state + action.payload;
default:
return state;
}
}
store/index.ts
import { combineReducers, ActionReducer, Action, StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { ModuleWithProviders } from '@angular/core';
import { compose } from '@ngrx/core';
import { simpleReducer } from "./simple/simple.reducer";
import { simpleEffects } from "./simple/simple.efficts";
export interface IAppState {
sum: number;
}
// all new reducers should be define here
const reducers = {
sum: simpleReducer
};
export const store: ModuleWithProviders = StoreModule.forRoot(reducers);
export const effects: ModuleWithProviders[] = [
EffectsModule.forRoot([simpleEffects])
];
app.module.ts
import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core';
import { effects, store } from "./Store/index";
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// store
store,
effects
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { IAppState } from './Store/index';
import { simpleActionTpye } from './Store/simple/simple.action';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
constructor(private store: Store<IAppState>) {
store.select(s => s.sum).subscribe((res) => {
console.log(res);
})
this.store.dispatch({
type: simpleActionTpye.add,
payload: 1
})
this.store.dispatch({
type: simpleActionTpye.add,
payload: 2
})
this.store.dispatch({
type: simpleActionTpye.add,
payload: 3
})
}
}
result 0 1 3 6
# Parameters
name | description |
---|---|
value | next value to be observed |
error | description |
err | error to be thrown |
super | description |
action$ | mock Observer that does nothing unless defined to do so in the mock class |
actionReducer$ | mock Observer that does nothing unless defined to do so in the mock class |
obs$ | mock Observable |
# Remarks
Observer is a generic, but must be of type any
to avoid unit testing complexity. The reason for this complexity, is that Store's constructor expects Observer arguments with different generic types. Using any
avoids this complication.
It's possible to pass null values into StoreMock's super constructor, but this restricts the number of assertions that can be used to test the class further down the road.
The Component being used in this example is just being used as context for how one would go about injecting Store as a provide in the test setup.
← Update typings ngrx →