[英]How to test ngrx router store selector
在我們的應用程序中,我們有一個簡單的存儲,在根級別包含一個AuthState
和一個RouterState
。 RouterState
是通過@ngrx/router-store
方法創建的。
我們有一些選擇器必須使用RouterState來檢索例如一個參數,然后將它與例如其他選擇器結果組合。
我們的問題是我們無法設法找到正確設置測試套件的方法,以便能夠測試這樣的組合選擇器。
StoreModule.forRoot(reducers, { metaReducers }),
StoreRouterConnectingModule.forRoot({
stateKey: 'router',
}),
StoreDevtoolsModule.instrument(),
reducers
如下:
export interface RouterStateUrl {
url: string;
queryParams: Params;
params: Params;
}
export interface State {
router: fromNgrxRouter.RouterReducerState<RouterStateUrl>;
auth: fromAuth.AuthState;
}
export const reducers: ActionReducerMap<State> = {
router: fromNgrxRouter.routerReducer,
auth: fromAuth.reducer,
};
export const getRouterState = createFeatureSelector<fromNgrxRouter.RouterReducerState<RouterStateUrl>>('router');
export const getRouterStateUrl = createSelector(
getRouterState,
(routerState: fromNgrxRouter.RouterReducerState<RouterStateUrl>) => routerState.state
);
export const isSomeIdParamValid = createSelector(
getRouterState,
(routerS) => {
return routerS.state.params && routerS.state.params.someId;
}
);
這是AuthState reducer:
export interface AuthState {
loggedIn: boolean;
}
export const initialState: AuthState = {
loggedIn: false,
};
export function reducer(
state = initialState,
action: Action
): AuthState {
switch (action.type) {
default: {
return state;
}
}
}
export const getAuthState = createFeatureSelector<AuthState>('auth');
export const getIsLoggedIn = createSelector(
getAuthState,
(authState: AuthState) => {
return authState.loggedIn;
}
);
export const getMixedSelection = createSelector(
isSomeIdParamValid,
getIsLoggedIn,
(paramValid, isLoggedIn) => paramValid && isLoggedIn
)
@Component({
template: ``
})
class ListMockComponent {}
describe('Router Selectors', () => {
let store: Store<State>;
let router: Router;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([{
path: 'list/:someId',
component: ListMockComponent
}]),
StoreModule.forRoot({
// How to add auth at that level
router: combineReducers(reducers)
}),
StoreRouterConnectingModule.forRoot({
stateKey: 'router',
}),
],
declarations: [ListMockComponent],
});
store = TestBed.get(Store);
router = TestBed.get(Router);
});
it('should retrieve routerState', () => {
router.navigateByUrl('/list/123');
store.select(getRouterState).subscribe(routerState => console.log(routerState));
});
{router:{state:{url:'/ list / 123',params:{someId:123},queryParams:{}},navigationId:1},auth:{loggedIn:false}}
正如你所看到的getRouterState
選擇不檢索只有router
的狀態,但片包含整個對象routerState
+ authState
State
。 router和auth是此對象的子級。 因此選擇器無法檢索正確的切片。
it('should retrieve routerStateUrl', () => {
router.navigateByUrl('/list/123');
store.select(getRouterStateUrl).subscribe(value => console.log(value));
});
undefined - TypeError:無法讀取未定義的屬性“state”
it('should retrieve mixed selector results', () => {
router.navigateByUrl('/list/123');
store.select(getMixedSelection).subscribe(value => console.log(value));
});
未定義
TypeError:無法讀取未定義的屬性“state”
TypeError:無法讀取{auth:{}的屬性'loggedIn',路由器:{}}
請注意語法
StoreModule.forRoot({
// How to add auth at that level
router: combineReducers(reducers)
}),
如果我們想要使用多個reducer組合選擇器,似乎是強制性的。 我們可以使用forRoot(reducers)
但是我們不能只測試路由器選擇器。 國家的其他部分將不存在。
例如,如果我們需要測試:
export const getMixedSelection = createSelector(
isSomeIdParamValid,
getIsLoggedIn,
(paramValid, isLoggedIn) => paramValid && isLoggedIn
)
我們需要路由器和auth。 我們找不到合適的測試設置,允許我們使用AuthState
和RouterState
測試這樣的組合選擇器。
如何設置此測試以便我們基本上可以測試我們的選擇器?
當我們運行應用程序時,它完美運行。 所以問題只在於測試設置。
我們認為使用真實路由器設置testBed可能是錯誤的想法。 但我們很難模擬routerSelector(僅)並給它一個模擬的路由器狀態片僅用於測試目的。
僅模擬這些路由器選擇器真的很難。 在store.select
上進行間諜活動很容易但是在store.select(routerSelectorMethod)
上進行間諜活動,方法作為參數變得一團糟。
我自己也在努力解決這個問題,路由器狀態的'state'屬性是未定義的。 我找到了適合我的解決方案是調用router.initialNavigation()來啟動RouterTestingModule,后者又設置了路由器存儲。
在我的情況下,我需要測試一個使用根存儲選擇器和功能存儲選擇器的CanActivate防護。 下面的測試模塊設置適用於我:
describe('My guard', () => {
let myGuard: MyGuard;
let router: Router;
let store: Store<State>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{
path: '',
redirectTo: 'one',
pathMatch: 'full'
},
{
path: 'one',
component: MockTestComponent
},
{
path: 'two',
component: MockTestComponent
}
]),
StoreModule.forRoot({
...fromRoot.reducers,
'myFeature': combineReducers(fromFeature.reducers)
}),
StoreRouterConnectingModule.forRoot({
stateKey: 'router', // name of reducer key
}),
],
declarations: [MockTestComponent],
providers: [MyGuard, {provide: RouterStateSerializer, useClass: CustomSerializer}]
}).compileComponents();
myGuard = TestBed.get(MyGuard);
router = TestBed.get(Router);
store = TestBed.get(Store);
spyOn(store, 'dispatch').and.callThrough();
router.initialNavigation();
}));
});
現在,您可以使用projector
屬性模擬選擇器依賴項:
我-reducer.ts
export interface State {
evenNums: number[];
oddNums: number[];
}
export const selectSumEvenNums = createSelector(
(state: State) => state.evenNums,
(evenNums) => evenNums.reduce((prev, curr) => prev + curr)
);
export const selectSumOddNums = createSelector(
(state: State) => state.oddNums,
(oddNums) => oddNums.reduce((prev, curr) => prev + curr)
);
export const selectTotal = createSelector(
selectSumEvenNums,
selectSumOddNums,
(evenSum, oddSum) => evenSum + oddSum
);
我-reducer.spec.ts
import * as fromMyReducers from './my-reducers';
describe('My Selectors', () => {
it('should calc selectTotal', () => {
expect(fromMyReducers.selectTotal.projector(2, 3)).toBe(5);
});
});
取自官方文檔
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.