Step 18

Testing Redux Actions

The first item of our project we will be testing are the Redux actions.

As mentioned in the previous step all of our spec files will live in a __specs__ directory beside the files we are testing.

First of all let's create this directory.

mkdir app/js/actions/__specs__

In this directory we will create a file called points.spec.js. It is important that spec files always end with .spec.js so that Jest can find them.

In this file we are going to import the two actions addFavourite and removeFavourite from the actions implementation file so that we can test what they return. We also import the two ADD_FAVOURITE and REMOVE_FAVOURITE constants that define the action types.

// app/js/actions/__specs__/points.spec.js

import { FAVOURITE_ADDED, FAVOURITE_REMOVED } from '../../constants';
import { addFavourite, removeFavourite } from '../';

Now we will begin by describing what we are testing. If you have used testing libraries in the past this should look familiar to you. If not, the describe is what we wrap around our tests to describe the context that we are testing in. For example here we have a top level describe stating that we are testing the points actions. We then have one context for each action, in this case "add" and "remove". The purpose of this is that a correct message is built in the terminal output while running your test that can easily be read. You will see this when we run the test.

You will notice that the describe (and also it that we will use in a moment) takes the description as the first argument and an arrow function as the second which will be where all of our test code is defined.

// app/js/actions/__specs__/points.spec.js

import { FAVOURITE_ADDED, FAVOURITE_REMOVED } from '../../constants';
import { addFavourite, removeFavourite } from '../';

describe('Points Actions', () => {
  describe('adding a favourite');
  describe('removing a favourite');
});

Now that we have our structure let's add the our test to for the "add" action. We define this using the it function. We could also use the test keyword since it is just an alias but the "it" allows you as a developer to read the spec as a sentence.

[the] Points Action adding a favourite, it builds the add action with the given ID in the payload.

Inside of the test we build a constant called expected which is the output we expect to receive when we actually run the addFavourite action.

// app/js/actions/__specs__/points.spec.js

  describe('Points Actions', () => {
-    describe('adding a favourite');
+     it('builds the add action with the given ID in the payload', () => {
+       const expected = {
+         type: FAVOURITE_ADDED,
+         payload: {
+           id: 42
+         }
+       };
+     });
+
    describe('removing a favourite');
  });

Now we will run the addFavourite action, store the returned action object into a constant called result, then check that this result matches our expected value.

// app/js/actions/__specs__/points.spec.js

  describe('Points Actions', () => {
    describe('adding a favourite', () => {
      it('builds the add action with the given ID in the payload', () => {
        const expected = {
          type: FAVOURITE_ADDED,
          payload: {
            id: 42
          }
        };
+       const result = addFavourite(42);
+
+       expect(result).toEqual(expected);
      });
    });

    describe('removing a favourite');
  });

We use the Jest expect function to given our expectation we can then chain on a "matcher" such as toEqual to complete the expectation. There are also other Jest matchers 1 such as toContain for arrays, toHaveBeenCalled for functions, and many more that you should look over in order to test in different ways.

We can now run the tests in our terminal by running yarn test. You will see an output similar to:

$ jest
 FAIL  app/js/actions/__specs__/points.spec.js
  Points Actions
    ✕ encountered a declaration exception (4ms)
    adding a favourite
      ✓ builds the action with the given ID in the payload (4ms)

  ● Points Actions › encountered a declaration exception

    TypeError: Cannot read property 'length' of undefined

      16 |     });
      17 |   });
    > 18 |   describe('removing a favourite');
      19 | });
      20 |

      at Env.describe (node_modules/jest-jasmine2/build/jasmine/Env.js:301:27)
      at Suite.<anonymous> (app/js/actions/__specs__/points.spec.js:18:3)
      at Object.<anonymous> (app/js/actions/__specs__/points.spec.js:4:1)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        0.758s, estimated 1s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Although our test "builds the add action with the given ID in the payload" passed it is complaining that we have a describe without a callback function.

In Jest you can always append the character "x" before a describe, it, or test to mark it as pending, meaning Jest will ignore trying to run that code. Add an "x" to the second describe.

// app/js/actions/__specs__/points.spec.js

   });

-   describe('removing a favourite');
+   xdescribe('removing a favourite');
  });

Run yarn test again and you will see the that our tests now succeed because one is passing and the other is pending (waiting for implementation).

$ jest
 PASS  app/js/actions/__specs__/points.spec.js
  Points Actions
    adding a favourite
      ✓ builds the action with the given ID in the payload (4ms)
    removing a favourite
      ○ skipped 1 test

Test Suites: 1 passed, 1 total
Tests:       1 skipped, 1 passed, 2 total
Snapshots:   0 total
Time:        1.045s
Ran all test suites.
✨  Done in 1.64s.

Finally let's add the test for removeFavourite. We will remove the "x" from the xdescribe, add a callback to the describe, add an it with an appropriate description and define our test with an expectation and result.

// app/js/actions/__specs__/points.spec.js

- xdescribe('removing a favourite');
+ describe('removing a favourite', () => {
+   it('builds the remove action with the given ID in the payload', () => {
+     const expected = {
+       type: FAVOURITE_REMOVED,
+       payload: {
+         id: 42
+       }
+     };
+     const result = removeFavourite(42);
+
+     expect(result).toEqual(expected);
+   });
+ });

Run yarn test in your terminal once more and see that now we have 2 passing tests.

$ jest
 PASS  app/js/actions/__specs__/points.spec.js
  Points Actions
    adding a favourite
      ✓ builds the add action with the given ID in the payload (7ms)
    removing a favourite
      ✓ builds the remove action with the given ID in the payload

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.515s
Ran all test suites.
✨  Done in 2.19s.

Now that the Redux actions are tested it makes sense to go onto the next step and test our Redux reducer which the actions are associated with.