如何在Angular 4单元测试中模拟@ngrx / store状态

时间:2017-08-23 20:49:13

标签: angular unit-testing jasmine

我正在使用@ ngrx / store和@ ngrx / effect开发一个Angular 4应用程序,我正在尝试为我的效果编写一些单元测试。

我的一个效果应该是使用服务从后端获取一些数据。从后端检索到值后,应每隔一段时间触发此效果,以生成轮询效果并更新本地数据。

由于轮询间隔是在我的应用程序状态下配置的,我希望能够模拟状态存储,以便我可以在测试期间更改它。

这是我的SPEC:

describe('Order Effects', () => {

  let actions: Observable<any>;
  let effects: OrderEffect;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        StoreModule.forRoot({ orders: ordersReducer })
      ],
      providers: [
        OrderEffect,
        provideMockActions(() => actions),
        { provide: OrderService, useValue: jasmine.createSpyObj('orderService', ['getAllOrders']) }
      ]
    });

    effects = TestBed.get(OrderEffect);
  });


  it('should load orders and call load orders complete action', () => {

    const mockData = [{ id: 1 }, { id: 2 }, { id: 3 }];

    const orderService = TestBed.get(OrderService);
    orderService.getAllOrders.and.returnValue(Observable.of(mockData));

    actions = hot('--a-', { a: new orderActions.LoadAllOrdersAction(new Date()) });

    const expectedEffect = cold('--b|', { b: new orderActions.LoadAllOrdersCompleteAction(mockData) });
    expect(effects.loadAllOrders$).toBeObservable(expectedEffect);
  });

});

这就是效果:

@Injectable()
export class OrderEffect {

  @Effect()
  loadAllOrders$: Observable<Action> = this.actions.ofType(orderActions.LOAD_ALL_ORDERS)
    .withLatestFrom(this.store$) // Get the latest state from the store and add as the second value of the array below
    .switchMap((input: any[]) => {
      const action: orderActions.LoadAllOrdersAction = input[0];
      const store: AppState = input[1];

      return Observable.timer(0, store.orders.pollingInterval) // Timer to create the polling effect.
        .switchMap(() => this.orderService.getAllOrders(action.payload)
          .map((orders: Array<any>) => new orderActions.LoadAllOrdersCompleteAction(orders))
          .catch(error => Observable.throw(new orderActions.LoadAllOrdersFailAction(error)))
        );
    });

  constructor(private actions: Actions, private orderService: OrderService, private store$: Store<AppState>) { }
}

在效果代码中,我使用:.withLatestFrom(this.store$)获取当前状态,我想模拟此属性:store.orders.pollingInterval。任何人都可以帮助我吗?

4 个答案:

答案 0 :(得分:8)

如果您需要在效果中获取商店状态,则在测试此效果时必须监视商店,以便您可以模拟其状态。

describe('Order Effects', () => {
    // ...
    let ordersStateMock = {
        orders: {
            pollingInterval: null
        }
    };

    beforeEach(() => {
        const storeSpy = jasmine.createSpyObj('storeSpy', [
            'dispatch', 'subscribe'
        ]);
        storeSpy.select = () => Observable.of(ordersStateMock);
        // ...
    });
});

然后在您所在的州,您可以为商店状态设置所需的值:

it('should load orders and call load orders complete action', () => {
    // ...
    ordersStateMock.orders.pollingInterval = 2;
    actions = hot('--a-', { a: new orderActions.LoadAllOrdersAction(new Date()) });
    // ...
});

我在测试中使用此解决方案。我在“使用Angular和ngrx进行反应性编程”一书中得到了它。它指的是ngrx和Angular v2,但它仍然非常有用,因为它解释了反应式编程和关键特性的概念。

希望这有帮助!

答案 1 :(得分:5)

我一直在查看@ngrx文档,我发现了另一个模拟商店状态的可能答案(参见下面的完整代码):

let actions;
let effects: OrderEffect;
let ordersStateMock;

beforeEach(() => {

  ordersStateMock = {
    orders: {
      pollingInterval: 30
    }
  };

  TestBed.configureTestingModule({
    imports: [
      StoreModule.forRoot(
        { orders: ordersReducer },
        { initialState: ordersStateMock }
      )
    ],
    providers: [
      OrderEffect,
      provideMockActions(() => actions),
      { provide: OrderService,
        useValue: jasmine.createSpyObj('orderService', ['getAllOrders'])
      }
    ]
  });

  effects = TestBed.get(OrderEffect);
});

显然我只需要在StoreModule导入中设置初始状态。

答案 2 :(得分:0)

建议的解决方案对我不起作用。我使用的是ngrx 5.2.0(带有ngrx实体)和Angular 5.2.9。 metaReducer的想法如下所述:https://github.com/ngrx/platform/issues/414#issuecomment-331354701

在我的情况下,我想模仿一些状态。所以我的metaReducer看起来像:

  export function mockMetaReducer(reducer: ActionReducer<any>): ActionReducer<any, any> {
    return function(state: any, action: any): any {
      return reducer(ordersStateMock, action);
    };
  }

答案 3 :(得分:0)

This solution是最简单的方法,它与 ngrx:5.2.0

一起使用

基于真实商店为商店创建模拟

export class StoreMock<T = any> extends Store<T> {

    public source = new BehaviorSubject<any>({});

    public overrideState(allStates: any) {
        this.source.next(allStates);
    }

}

并在测试中使用它

let store: StoreMock<YourStateModel>;

TestBed.configureTestingModule({
    providers: [
        { provide: Store, useClass: StoreMock },
    ]
});
store = TestBed.get(Store);


beforeEach(() => {
    store.overrideState({
        currentStateName: MOCKED_STATE_DATA_OBJECT
    });
});