npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

jest-mock-action-creators

v1.2.1

Published

Easy mock & test redux action creators with dispatch() in your components

Downloads

4,168

Readme

Mock redux action creators with jest

Why

You're cool. You're going to test your React smart components (aka containers in redux) and make assumptions what they call necessary redux action creators. Well, you can have few ways to achieve this:

Path 1: action creators binding


class Comp extends Component {
    render() {
        // some render... not important
    }

    onSomeButtonClick() {
        this.props.sendMyAction();
    }
}

function mapDispatchToProps(dispatch) {
    return { sendMyAction: bindActionCreators(dispatch, sendMyAction) }
};

then in test

const spy = jest.fn();
shallow(<Comp sendMyAction={spy} />);

It works well in plain Javascript, but quickly becomes headache in Typescript:

interface Actions {
    sendMyAction: typeof sendMyAction; // What??
    sendMyOtherAction: typeof sendMyOtherAction;
    actions: typeof actionBundle;
}

class Comp extends Component<Actions> {
    // ....
}

You must to declare your actions here to not lose the function types, which is the boring task. Also, you may have some middleware which converts your action to promises/observables (very common case) and you'll probably be ended in declaring separate function types with promisified results.

Path 2: By using injected dispatch() and call action creators directly

import { sendMyAction } from "./actions";

class Comp extends Component {
    onSomeButtonClick() {
        this.props.dispatch(sendMyAction());
    }
}

This is also neat in Typescript (since you don't need to declare your actions in component props again) and you can do some tricks with generics and redux's dispatch() to automatically obtain correct result type from request action type. Nice!

Unfortunately this way has very big disadvantages when it comes to testing:

  1. You're depending on actual action creator function in your component test, which is not very good (Action creator may have some logic additionally):
  2. Hard to test
  3. Not possible to test thunks action creators
  4. In case if it will be handled by some middleware which will return different result (for ex. promise), you must mock your action creator

For example:

function myAction(a, b) {
    return {
        type: "MY_ACTION",
        payload: {
            a: a === true ? "truthy": "falsy",
            b
        }
    }
}

class C extends Component {
    someButtonClick() {
        this.props.dispatch(myAction(true, 5));
    }
}

test:

const dispatch = jest.fn();
const w = shallow(<Component dispatch={dispatch} />);
w.find("button").simulate("click");
  1. You must assert your dispatch in test with something like:
expect(dispatch).toBeCalledWith(myAction(true, 5));
// or
expect(dispatch).toBeCalledWith({ type: "MY_ACTION", payload: { a: "truthy", b: 5 }});

Variant 1 looks OK, but is not always achievable. For example if you have some param validation in your creator and throw error - it won't be possible to test it without mocking AC.

  1. Thunk action creators won't work
function myAction(a, b) {
    return dispatch => {
        dispatch(someOtherAction());
    }
}
expect(dispatch).toBeCalledWith(myAction(true, 5));

Will always fail

  1. Mocking action:
class C extends Component {
    async someButtonClick() {
        const res = await this.props.dispatch(myAction(true, 5));
        // do something with res
        res.someval;
    }
}

//test

import { myAction } from "../actions";
jest.mock("../actions");

myAction.mockReturnValue({ someval: 1 });

Again, this will quickly become very boring if you have many actions creators and even much boring when using Typescript

This library comes to resque:

class C extends React.Component {
    render() {
        // some render
    }

    buttonClick() {
        this.props.dispatch(anotherAction("test"));
    }

    async anotherButtonClick() {
        const res = await this.props.dispatch(myAction(true, 5, "prop"));
        await this.props.dispatch(anotherAction(res.val));
    }
}

test:

import { mockActionCreators, createDispatchMockImplementation } from "jest-mock-action-creators";
import { myAction } from "../myAction";
import { anotherAction } from "../anotherAction";

// Automatically mock your action creator modules. When using babel transformer it will insert jest.mock() for their module paths
mockActionCreators(myAction, anotherAction);
// or replaceActionCreators(myAction, anotherAction); // Doesn't insert jest.mock() for their module paths, expects myAction and anotherAction be already mocked (i.e. jest.fn())

const dispatch = jest.fn();
const wrapper = shallow(<C dispatch={dispatch} />);
wrapper.find("button").simulate("click");

// Pretty easy, isn't it?
expect(dispatch).toBeCalledWithActionCreator(anotherAction, "test");
expect(dispatch).not.toBeCalledWithActionCreator(myAction);

// Return { val: "test2" } when calling myAction();
createDispatchMockImplementation(dispatch, {
    [myAction.name]: { val: "test2" }
});

wrapper.find("anotherButton").simulate("click");
expect(dispatch).toBeCalledWithActionCreator(myAction, true, 5, "prop");
expect(dispatch).toBeCalledWithActionCreator(anotherAction, "test2");

Installation

npm install jest-mock-action-creators --save-dev

Add jest-mock-action-creators/babel to your plugins in .babelrc or .babelrc.js

When using typescript and ts-jest, enable babel processing in ts-jest (enabled by default) and tell it to use .babelrc:

{
  "jest": {
    "globals": {
      "ts-jest": {
        "useBabelrc": true
      }
    }
  }
}

Note: Specifying plugins: [] in ts-jest babel configuration won't work

and finally import in your test:

import { mockActionCreators, createDispatchMockImplementation } from "jest-mock-action-creators";

Want to mock only the specified action creators?

It's possible with using jest-easy-mock, use this configuration:

        ["jest-easy-mock", {
            requireActual: true,
            identifiers: [
                {
                    name: "jest.mockObj",
                    remove: true,
                    type: "name",
                },
                {
                    name: "jest.mockFn",
                    remove: true,
                    type: "mock",
                },
                {
                    name: "replaceActionCreators",
                    remove: false,
                    type: "mock"
                }
            ]
        }],
        ["jest-mock-action-creators/babel", { mockIgnoreExpressions: ["mock", "doMock", "mockObj", "mockFn"] }],

and in the test:

import { myAction, myAction2 } from "../actions";
import { ActionModule } from "../../module";

beforeEach(() => {
    replaceActionCreators(
        myAction,
        ActionModule.action1,
    );
});

it("test", () => {
    myAction(); // mocked
    myAction2(); // non-mocked

    ActionModule.action1(); // mocked
    ActionModule.action2(); // non-mocked
});