在我们的应用中,我们有一个简单的商店,在根级别包含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));
});
{路由器: {state:{url:&#39; / list / 123&#39;,params:{someId:123},queryParams:{}}, navigationId:1}, AUTH: {loggedIn:false}}
正如您所看到的,getRouterState
选择器不会仅检索州的router
切片,而是包含整个routerState
+ authState
{{的对象1}}。 router和auth是此对象的子级。因此,选择器无法检索正确的切片。
State
undefined - TypeError:无法读取属性&#39; state&#39;未定义的
it('should retrieve routerStateUrl', () => {
router.navigateByUrl('/list/123');
store.select(getRouterStateUrl).subscribe(value => console.log(value));
});
未定义
TypeError:无法读取属性&#39; state&#39;未定义的
TypeError:无法读取属性&#39; loggedIn&#39; of {auth:{},router:{}}
请注意语法
it('should retrieve mixed selector results', () => {
router.navigateByUrl('/list/123');
store.select(getMixedSelection).subscribe(value => console.log(value));
});
如果我们想要使用多个reducer组合选择器,似乎是强制性的。我们可以使用StoreModule.forRoot({
// How to add auth at that level
router: combineReducers(reducers)
}),
但我们不能仅测试路由器选择器。国家的其他部分将不存在。
例如,如果我们需要测试:
forRoot(reducers)
我们需要路由器和身份验证。我们找不到合适的测试设置,允许我们使用export const getMixedSelection = createSelector(
isSomeIdParamValid,
getIsLoggedIn,
(paramValid, isLoggedIn) => paramValid && isLoggedIn
)
和AuthState
来测试这样的组合选择器。
如何设置此测试以便我们基本上可以测试我们的选择器?
当我们运行应用程序时,它完美运行。所以问题只在于测试设置。
我们认为使用真实路由器设置testBed可能是错误的想法。但是我们很难模拟routerSelector(仅)并给它一个模拟的路由器状态片仅用于测试目的。
仅仅模拟这些路由器选择器真的很难。对RouterState
进行间谍活动很容易,但在store.select
上进行间谍活动,方法作为参数变得一团糟。
答案 0 :(得分:2)
本人为此而苦苦挣扎,routerState的'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();
}));
});
答案 1 :(得分:1)
现在,您可以使用projector
属性来模拟选择器依赖项:
my-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
);
my-reducer.spec.ts
import * as fromMyReducers from './my-reducers';
describe('My Selectors', () => {
it('should calc selectTotal', () => {
expect(fromMyReducers.selectTotal.projector(2, 3)).toBe(5);
});
});