Angular 2
Wyśmiewanie @ ngrx / Store
Szukaj…
Wprowadzenie
Parametry
Nazwa | opis |
---|---|
wartość | następna wartość do zaobserwowania |
błąd | opis |
błądzić | błąd do zgłoszenia |
Wspaniały | opis |
akcja $ | próbny obserwator, który nic nie robi, chyba że jest to określone w klasie próbnej |
actionReducer $ | próbny obserwator, który nic nie robi, chyba że jest to określone w klasie próbnej |
obs $ | drwiący obserwowalny |
Uwagi
Obserwator jest ogólny, ale musi być any
typu, aby uniknąć złożoności testów jednostkowych. Powodem tej złożoności jest to, że konstruktor Store oczekuje argumentów Observer z różnymi rodzajami rodzajowymi. Użycie any
pozwala uniknąć tej komplikacji.
Możliwe jest przekazanie wartości null do superkonstruktora StoreMock, ale ogranicza to liczbę twierdzeń, które można wykorzystać do przetestowania klasy w dalszej części drogi.
Komponent użyty w tym przykładzie jest po prostu używany jako kontekst, w jaki sposób można wstrzykiwać Store jako źródło w konfiguracji testowej.
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();
});
});
Test jednostkowy elementu z próbnym sklepem
Jest to test jednostkowy komponentu, który ma zależność Store . Tutaj tworzymy nową klasę o nazwie MockStore, która jest wstrzykiwana do naszego komponentu zamiast zwykłego 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();
}));
Test jednostkowy szpiegowania komponentów w sklepie
Jest to test jednostkowy komponentu, który ma zależność Store . W tym przypadku możemy użyć sklepu z domyślnym „stanem początkowym”, jednocześnie zapobiegając faktycznemu wywoływaniu akcji po wywołaniu metody store.dispatch () .
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 Observable (service + component)
usługa
- Stworzyłem usługę pocztową metodą 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());
}
}
Składnik
- Utworzyłem komponent z parametrem wyniku i funkcją postExample, która wywołuje postService.
- gdy ponowne żądanie zakończone powodzeniem niż parametr wyniku powinno mieć wartość „Sukces”, inaczej „Niepowodzenie”
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'
)
}
}
usługa testowa
- gdy chcesz przetestować usługę korzystającą z http, powinieneś użyć mockBackend. i wstrzyknij to.
- musisz również wstrzyknąć 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();
});
}));
});
element testowy
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);
});
}));
});
Prosty sklep
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
})
}
}
wynik 0 1 3 6