Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest: tests can't fail within setImmediate or process.nextTick callback

I'm trying to write a test for a React component that needs to complete an asynchronous action in its componentWillMount method. componentWillMount calls a function, passed as a prop, which returns a promise, and I mock this function in my test.

This works fine, but if a test fails in a call to setImmediate or process.nextTick, the exception isn't handled by Jest and it exits prematurely. Below, you can see I even try to catch this exception, to no avail.

How can I use something like setImmediate or nextTick with Jest? The accepted answer to this question is what I'm trying to implement unsuccessfully: React Enzyme - Test `componentDidMount` Async Call.

it('should render with container class after getting payload', (done) => {
  let resolveGetPayload;
  let getPayload = function() {
    return new Promise(function (resolve, reject) {
      resolveGetPayload = resolve;
    });
  }
  const enzymeWrapper = mount(<MyComponent getPayload={getPayload} />);

  resolveGetPayload({
    fullname: 'Alex Paterson'
  });

  try {
    // setImmediate(() => {
    process.nextTick(() => {
      expect(enzymeWrapper.hasClass('container')).not.toBe(true); // Should and does fail
      done();
    });
  } catch (e) {
    console.log(e); // Never makes it here
    done(e);
  }
});

Jest v18.1.0

Node v6.9.1

like image 637
Alex Paterson Avatar asked Jan 22 '17 15:01

Alex Paterson


People also ask

What is the difference between process nextTick () and setImmediate ()?

1. process. nextTick() is used to schedule a callback function to be invoked in the next iteration of the Event Loop. setImmediate() method is used to execute a function right after the current event loop finishes.

What is process nextTick?

nextTick. A method of the native Node process module, process. nextTick is similar to the familiar setTimeout method in which it delays execution of its callback function until some point in the future.

Why is my Jest test hanging?

If you noticed that Jest (npm / yarn) test hang at the end of the test execution while you run tests in parallel on your CI server with @knapsack-pro/jest client then you probably have open handles preventing Jest from exiting cleanly. Another tip: you can also verify if you have enough memory on CI server.


1 Answers

Wrapping the callback block passed to process.nextTick or setImmediate in a try/catch works, as others have shown, but this is verbose and distracting.

A cleaner approach is to flush promises using the brief line await new Promise(setImmediate); inside an async test callback. Here's a working example of using this to let an HTTP request in a useEffect (equally useful for componentDidMount) resolve and trigger a re-render before running assertions:

Component (LatestGist.js):

import axios from "axios";
import React, {useState, useEffect} from "react";

export default () => {
  const [gists, setGists] = useState([]);

  const getGists = async () => {
    const res = await axios.get("https://api.github.com/gists");
    setGists(res.data);
  };    
  useEffect(() => {
    getGists();
  }, []);

  return (
    <>
      {gists.length
        ? <div data-test="test-latest-gist">
            the latest gist was made on {gists[0].created_at} 
            by {gists[0].owner.login}
          </div>
        : <div>loading...</div>}
    </>
  );
};

Test (LatestGist.test.js):

import React from "react";
import {act} from "react-dom/test-utils";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({adapter: new Adapter()});
import mockAxios from "axios";
import LatestGist from "../src/components/LatestGist";

jest.mock("axios");

describe("LatestGist", () => {
  beforeEach(() => jest.resetAllMocks());
  
  it("should load the latest gist", async () => {
    mockAxios.get.mockImplementationOnce(() => 
      Promise.resolve({ 
        data: [
          {
            owner: {login: "test name"},
            created_at: "some date"
          }
        ],
        status: 200
      })
    );

    const wrapper = mount(<LatestGist />);
    let gist = wrapper
      .find('[data-test="test-latest-gist"]')
      .hostNodes()
    ;
    expect(gist.exists()).toBe(false);

    await act(() => new Promise(setImmediate));
    wrapper.update();

    expect(mockAxios.get).toHaveBeenCalledTimes(1);
    gist = wrapper
      .find('[data-test="test-latest-gist"]')
      .hostNodes()
    ;
    expect(gist.exists()).toBe(true);
    expect(gist.text()).toContain("test name");
    expect(gist.text()).toContain("some date");
  });
});

Forcing a failed assertion with a line like expect(gist.text()).toContain("foobar"); doesn't cause the suite to crash:

● LatestGist › should load the latest gist

expect(string).toContain(value)

  Expected string:
    "the latest gist was made on some date by test name"
  To contain value:
    "foobar"

    at Object.it (src/LatestGist.test.js:30:25)

Here are my dependencies:

{
  "dependencies": {
    "axios": "^0.18.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "devDependencies": {
    "enzyme": "3.9.0",
    "enzyme-adapter-react-16": "1.12.1",
    "jest": "24.7.1",
    "jest-environment-jsdom": "24.7.1"
  }
}
like image 160
ggorlen Avatar answered Sep 18 '22 16:09

ggorlen