4

在我们的应用程序中,我们有一个简单的商店,在根级别包含 anAuthState和 a RouterStateRouterState是通过@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 减速器:

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);
  });

测试及其结果

测试 1

it('should retrieve routerState', () => {
  router.navigateByUrl('/list/123');
  store.select(getRouterState).subscribe(routerState => console.log(routerState));
});

{路由器:{状态:{url:'/list/123',参数:{someId:123},queryParams:{}},navigationId:1},身份验证:{loggedIn:false}}

如您所见,getRouterState选择器不仅检索router状态切片,而且检索包含整个routerState+的对象authState State。router 和 auth 是这个对象的子对象。因此选择器无法检索正确的切片。

测试 2

it('should retrieve routerStateUrl', () => {
  router.navigateByUrl('/list/123');
  store.select(getRouterStateUrl).subscribe(value => console.log(value));
});

未定义 - 类型错误:无法读取未定义的属性“状态”

测试 3

it('should retrieve mixed selector results', () => {
  router.navigateByUrl('/list/123');
  store.select(getMixedSelection).subscribe(value => console.log(value));
});

不明确的

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
)

我们需要路由器和身份验证。而且我们找不到合适的测试设置来允许我们使用AuthStateand来测试这样的组合选择器RouterState

问题

如何设置这个测试,以便我们基本上可以测试我们的选择器?

当我们运行该应用程序时,它运行良好。所以问题只出在测试设置上。

我们认为使用真实路由器设置测试床可能是一个错误的想法。但是我们很难模拟 routerSelector(仅)并给它一个模拟的路由器状态切片,仅用于测试目的。

仅模拟这些路由器选择器真的很难。监视store.select很容易,但是监视store.select(routerSelectorMethod), 方法作为参数变得一团糟。

4

2 回答 2

4

我自己也在为此苦苦挣扎,routerState 的“状态”属性是未定义的。我发现对我有用的解决方案是调用 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();
   }));
});
于 2018-06-26T08:57:29.083 回答
1

projector现在您可以使用属性模拟选择器依赖项:

我的减速器.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
);

我的减速器.spec.ts

import * as fromMyReducers from './my-reducers';

describe('My Selectors', () => {

  it('should calc selectTotal', () => {
    expect(fromMyReducers.selectTotal.projector(2, 3)).toBe(5);
  });

});

取自官方文档

于 2018-10-08T13:59:21.840 回答