Testing Complex Actions

Things get a little trickier when we want to test asynchronous or conditionalaction creators. Our goal is still the same: make sure that our operations aredispatching the actions we're expecting.

A Conditional Action

Consider the following conditional action (i.e., one that is fired dependingon current state):

  1. import { Injectable } from '@angular/core';
  2. import { NgRedux } from 'ng2-redux';
  3. export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
  4. @Injectable()
  5. export class MyActionService {
  6. constructor(private redux: NgRedux) {};
  7. // A conditional action
  8. incrementIfOdd() {
  9. const { counter } = this.redux.getState();
  10. if (counter % 2 === 0) return;
  11. this.redux.dispatch({ type: INCREMENT_COUNTER });
  12. }
  13. }

Unit testing is similar to before, but we need to mock our state as well as dispatch:

  1. import { NgRedux } from 'ng2-redux';
  2. import { CounterActions } from './counter';
  3. class MockRedux extends NgRedux<any> {
  4. constructor(private state: any) {
  5. super(null);
  6. }
  7. dispatch = () => undefined;
  8. getState = () => this.state;
  9. }
  10. describe('counter action creators', () => {
  11. let actions: CounterActions;
  12. let mockRedux: NgRedux<any>;
  13. let mockState: any = {};
  14. beforeEach(() => {
  15. // Our mock NgRedux can now accept mock state as a constructor param.
  16. mockRedux = new MockRedux(mockState);
  17. actions = new CounterActions(mockRedux);
  18. });
  19. it('incrementIfOdd should dispatch INCREMENT_COUNTER action if count is odd',
  20. () => {
  21. // Our tests can bake in the initial state they need.
  22. const expectedAction = {
  23. type: CounterActions.INCREMENT_COUNTER
  24. };
  25. mockState.counter = 3;
  26. spyOn(mockRedux, 'dispatch');
  27. actions.incrementIfOdd();
  28. expect(mockRedux.dispatch).toHaveBeenCalled();
  29. expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
  30. });
  31. it('incrementIfOdd should not dispatch INCREMENT_COUNTER action if count is even',
  32. () => {
  33. mockState.counter = 2;
  34. spyOn(mockRedux, 'dispatch');
  35. actions.incrementIfOdd();
  36. expect(mockRedux.dispatch).not.toHaveBeenCalled();
  37. });
  38. });

An Async Action

What about async actions like the following?

  1. import { Injectable } from '@angular/core';
  2. import { NgRedux } from 'ng2-redux';
  3. export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
  4. export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
  5. @Injectable()
  6. export class CounterActions {
  7. constructor(private redux: NgRedux<any>) {}
  8. // ...
  9. incrementAsync(timeInMs = 1000) {
  10. this.delay(timeInMs).then(() => this.redux.dispatch({ type: INCREMENT_COUNTER }));
  11. }
  12. private delay(timeInMs) {
  13. return new Promise((resolve,reject) => {
  14. setTimeout(() => resolve() , timeInMs);
  15. });
  16. }
  17. }

We can test this using the normal techniques for async service functions:

  • If we can make incrementAsync return a promise, we can just return a promisefrom the test and jasmine will wait until it settles.
  • Alternately, we can use the fakeAsync technique discussed in the section ontesting components.
    The thing to remember is that if we follow the ActionCreatorServicepattern, our actions are just functions on an Angular service. So we can mockout NgRedux (and any other dependencies) and just test it as we would any otherAngular service.

原文: https://angular-2-training-book.rangle.io/handout/testing/redux/complex-actions.html