Angular 2
Mocking @ ngrx / Store
Sök…
Introduktion
parametrar
namn | beskrivning |
---|---|
värde | nästa värde som ska observeras |
fel | beskrivning |
fela | fel som ska kastas |
super | beskrivning |
action $ | mock Observer som inte gör något om det inte är definierat att göra det i håravklassen |
actionReducer $ | mock Observer som inte gör något om det inte är definierat att göra det i håravklassen |
obs $ | håna observerbar |
Anmärkningar
Observatören är en generisk men måste vara av typen any
att undvika enhetstestkomplexitet. Anledningen till denna komplexitet är att Butiks konstruktör förväntar sig observatörsargument med olika generiska typer. Genom att använda any
undviks denna komplikation.
Det är möjligt att överföra nollvärden till StoreMocks superkonstruktör, men detta begränsar antalet påståenden som kan användas för att testa klassen längre ner på vägen.
Komponenten som används i det här exemplet används bara som sammanhang för hur man skulle gå till att injicera Store som en tillhandahållande i testuppsättningen.
Observerar håna
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();
});
});
Enhetstest för komponent med Mock Store
Detta är ett enhetstest av en komponent som har Store som ett beroende. Här skapar vi en ny klass som heter MockStore som injiceras i vår komponent istället för den vanliga butiken.
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();
}));
Enhetstest för spionering av komponenter i butik
Detta är ett enhetstest av en komponent som har Store som ett beroende. Här kan vi använda en butik med standard "initialtillstånd" samtidigt som vi förhindrar att den faktiskt skickar åtgärder när store.dispatch () anropas.
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!');
});
}));
});
Angular 2 - Mock Obsable (service + komponent)
service
- Jag skapade posttjänst med metoden postRequest.
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());
}
}
Komponent
- Jag skapade komponent med resultatparameter och postExample-funktion som ringer till postService.
- när postfrågeställningen lyckades än parametern ska vara "Framgång" annars "Misslyckas"
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'
)
}
}
testtjänst
- när du vill testa tjänsten som att använda http bör du använda mockBackend. och injicera den till den.
- du behöver också injicera 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();
});
}));
});
testkomponent
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);
});
}));
});
Enkel butik
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;
}
}
butiks / 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
})
}
}
resultat 0 1 3 6