1

I am trying to run a Jest test for a network fetching in my integration test. It used beforeEach to create a fake network fetching response, and it should return a response of length 2. The test goes well when I remove the done from the following code, however as long as I used done as the callback and it seems failed the test and the error suggest there is only length of 1 returned, while the expected length should be 2.

It's using Enzyme and full dom for testing the integration of 3 components, and the test went well when I don't use the done, but as soon as I used done it failed the test.

beforeEach(() => {
    moxios.install();
    moxios.stubRequest('http://jsonplaceholder.typicode.com/comments', {
        status: 200,
        response: [ { name: 'Fetch #1' }, { name: 'Fetch #2' } ]
    });
});

afterEach(() => {
    moxios.uninstall();
});

it('can fetch a list of comments and display them', (done) => {
    // Attempt to render the *entire* app
    const wrapped = mount(
        <Root>
            <App />
        </Root>
    );
    // find the 'fetchComments' button and click it
    wrapped.find('.fetch_comments').simulate('click');

    // setTimeout is used because moxio introduces a little delay fetching the data.
    // so setTimeout makes Jest to have a little delay so it won't throw error.
    //  Expect to find a list of comments!
    //
    setTimeout(() => {
        wrapped.update();
        console.log(wrapped.find('li').length);
        expect(wrapped.find('li').length).toEqual(2);

        wrapped.unmount();
        done();
    }, 3300);

});
      1
    console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
      Error: Uncaught [Error: expect(received).toEqual(expected) // deep equality

      Expected: 2
      Received: 1]
          at reportException (/Users/dmml/Documents/Developer/reactPractice/testing/testing-stephen-grider/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
          at Timeout.callback [as _onTimeout] (/Users/dmml/Documents/Developer/reactPractice/testing/testing-stephen-grider/node_modules/jsdom/lib/jsdom/browser/Window.js:680:7)
          at listOnTimeout (internal/timers.js:531:17)
          at processTimers (internal/timers.js:475:7) JestAssertionError: expect(received).toEqual(expected) // deep equality

      Expected: 2
      Received: 1
          at toEqual (/Users/dmml/Documents/Developer/reactPractice/testing/testing-stephen-grider/src/__tests__/integrations.test.js:36:37)
          at Timeout.callback [as _onTimeout] (/Users/dmml/Documents/Developer/reactPractice/testing/testing-stephen-grider/node_modules/jsdom/lib/jsdom/browser/Window.js:678:19)
          at listOnTimeout (internal/timers.js:531:17)
          at processTimers (internal/timers.js:475:7) {
        matcherResult: {
          actual: 1,
          expected: 2,
          message: [Function],
          name: 'toEqual',
          pass: false
        }
      }

  ● can fetch a list of comments and display them

    expect(received).toEqual(expected) // deep equality

    Expected: 2
    Received: 1

      34 |              wrapped.update();
      35 |              console.log(wrapped.find('li').length);
    > 36 |              expect(wrapped.find('li').length).toEqual(2);
         |                                                ^
      37 | 
      38 |              wrapped.unmount();
      39 |              done();

      at toEqual (src/__tests__/integrations.test.js:36:37)
      at Timeout.callback [as _onTimeout] (node_modules/jsdom/lib/jsdom/browser/Window.js:678:19)

Test Suites: 1 failed, 2 passed, 3 total
Tests:       1 failed, 5 passed, 6 total
Snapshots:   0 total
Time:        5.028s
Ran all test suites related to changed files.
skyboyer
  • 22,209
  • 7
  • 57
  • 64
DSL
  • 31
  • 4

1 Answers1

0

I've never had good success with moxios. As such, I prefer mock-axios-adapter. If you're using thenables you can return the axios call within the class field and await the response. Otherwise, if your using async/await you can simply Promise.resolve() the class field instance. In addition, I try to avoid using setTimeout as much as possible as it adds to overall test running time.

For example (thenables):

class Example extends Component {
  state = {
    data: [],
    hasError: "",
    isLoading: true
  };

  componentDidMount = () => {
    window.setTimeout(() => {
      this.fetchData("users");
    }, 1500);
  };

  fetchData = url => {
    return app
      .get(`${url}`)
      .then(res => {
        this.setState({ isLoading: false, data: res.data });
      })
      .catch(err =>
        this.setState({ isLoading: false, hasError: err.toString() })
      );
  }

  handleClick = () => {
    this.setState({ isLoading: true, data: [] }, () => {
      this.fetchData("todos");
    });
  };

  render = () => (
    <div className="app-container">
      {this.state.isLoading ? (
        <ShowLoading />
      ) : this.state.hasError ? (
        <ShowError error={this.state.hasError} />
      ) : (
        <ShowData data={this.state.data} handleClick={this.handleClick} />
      )}
    </div>
  );
}

For example (async/await):

class Example extends Component {
  state = {
    data: [],
    hasError: "",
    isLoading: true
  };

  componentDidMount = () => {
    window.setTimeout(() => {
      this.fetchData("users");
    }, 1500);
  };

  fetchData = async url => {
    try {
      const res = await app.get(`${url}`);
      this.setState({ isLoading: false, data: res.data });
    } catch (err) {
      this.setState({ isLoading: false, hasError: err.toString() });
    }
  };

  handleClick = () => {
    this.setState({ isLoading: true, data: [] }, () => {
      this.fetchData("todos");
    });
  };

  render = () => (
    <div className="app-container">
      {this.state.isLoading ? (
        <ShowLoading />
      ) : this.state.hasError ? (
        <ShowError error={this.state.hasError} />
      ) : (
        <ShowData data={this.state.data} handleClick={this.handleClick} />
      )}
    </div>
  );
}

Then in your tests invoke and resolve fetchData (works for both examples -- I use the word implementation to refer to how a fake API request should interact with the component(s) and integration to refer to how a real API will interact with the component(s) -- but that's all subjective):

import React from "react";
import MockAdapter from "axios-mock-adapter";
import { shallow } from "enzyme"; // I use shallow, but you can use mount
import Users from "../index.js";
import app from "../../../utils/axiosConfig";

const mockAxios = new MockAdapter(app);

const data = [
  {
    id: 1,
    name: "Leanne Graham",
    username: "Bret",
    email: "Sincere@april.biz",
    address: {
      street: "Kulas Light",
      suite: "Apt. 556",
      city: "Gwenborough",
      zipcode: "92998-3874",
      geo: {
        lat: "-37.3159",
        lng: "81.1496"
      }
    },
    phone: "1-770-736-8031 x56442",
    website: "hildegard.org",
    company: {
      name: "Romaguera-Crona",
      catchPhrase: "Multi-layered client-server neural-net",
      bs: "harness real-time e-markets"
    }
  }
];

describe("App", () => {
  let wrapper;
  beforeEach(() => {
    mockAxios.reset();
    wrapper = shallow(<Users />);
  });

  afterAll(() => {
    wrapper.unmount();
  });

  it("renders without errors", () => {
    expect(wrapper.find("div.app-container")).toHaveLength(1);
  });

  it("initally shows that it's loading", () => {
    expect(wrapper.state("isLoading")).toBeTruthy();
    expect(wrapper.find("ShowLoading")).toHaveLength(1);
  });

  describe("API Implementation", () => {
    it("renders data and shows an Update button", async () => {
      mockAxios.onGet("users").reply(200, data);
      await Promise.resolve(wrapper.instance().fetchData("users"));

      expect(wrapper.state("isLoading")).toBeFalsy();
      expect(
        wrapper
          .find("ShowData")
          .dive()
          .find("div.data")
      ).toHaveLength(1);
      expect(
        wrapper
          .find("ShowData")
          .dive()
          .find("button.update")
      ).toHaveLength(1);
      mockAxios.restore();
    });
  });

  describe("API Integration", () => {
    it("retrieves real data from API, renders data, and shows an Update button", async () => {
      await Promise.resolve(wrapper.instance().fetchData("users"));

      expect(wrapper.state("isLoading")).toBeFalsy();
      expect(
        wrapper
          .find("ShowData")
          .dive()
          .find("div.data")
      ).toHaveLength(10);
      expect(
        wrapper
          .find("ShowData")
          .dive()
          .find("button.update")
      ).toHaveLength(1);
    });
  });
});

Run the Tests tab located next to Browser.

Working example (thenables):

Edit API Example w/ Axios Mock Testing (thenables)

Working example (async/await):

Edit API Example w/ Axios Mock Testing (async/await)

Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51