简体   繁体   中英

Testing a React component with Jest / Enzyme & Axios

I have a react component that uses Axios to fetch data:

import React from "react";
import axios from "axios";

export default class Example extends React.Component {
    constructor() {
        super();
        axios("http://localhost/data.json").then(response => {
            this.setState({
                data: response.data
            });
        });
    }

    render() {
        return <ul>{this.data.map(item => <li>{item}</li>)}</ul>;
    }
}

I'm trying to write some basic tests around this component. First I just wanted the component to render...

import React from "react";
import { shallow } from "enzyme";

import Example from "./example";

it.only("Renders", () => {
    const wrapper = shallow(<Example />);
    const li = wrapper.find("li");
    expect(li.length).toBe(2); // there are two items in the data file
});

...but the first thing I get is a network error from axios.

I'm new to testing and I know for the testing of components I need to get into mocking etc, but I was under the impression that you could still use live endpoints although it's not optimal.

Is there a way to tell Jest to wait until the component has rendered before making the assertions?

Any advice would be appreciated!

I used spies ( sinonjs ) to test code with asynchronous calls. The idea with spies is to "spy" on a particular function (ie your call back), and assert on whether or not is was called and/or how many times it was called, etc.

A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls.

let wrapper = shallow(<ReactComponent/>);
let handleEventSpy = sinon.spy(ReactComponentFile, 'functionName');

it('Event Handled', () => {
  const event = {... event stuffs ...};
  expect(handleEventSpy.called).tobe(false);
  wrapper.simulate('Event', event);
  expect(handleEventSpy.called).tobe(true)
}

You can also test asynchronous functions with Mocha , where the idea is to wrap the code called an async function passing it a special function that the test can call after the async function has completed. Mocha also works well with promises and has an even clear syntax.

I'd recommend:

  1. Factor your response handler into it's own function
    • Allows testing in isolation from axios using specific test data
    • Depending on how often data updates, there's a performance increase not binding a new function on every call
  2. Factor your list renderer into it's own function
    • Same as above
    • Combined with lifecycle hooks, avoids calling the map function on a null object
  3. Utilize React's component lifecycle hooks
  4. Although not a requirement, it's good practice to pass props in the constructor to the parent class
  5. As suggested, mock axios for testing to avoid making long running asynchronous tasks and use test data objects instead.
    • jest.mock('axios');

import React from "react";
import axios from "axios";

export default class Example extends React.Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    axios("http://localhost/data.json")
      .then(response => this.responseHandler)
      .catch(err => this.errorHandler);
  }

  responseHandler = ({data}) => this.setState({data});
  errorHandler = (err) => { ... handle error ...};
  renderRows = ({data}) = <>{data.map(item => this.renderRow}</>; // React Fragment, fyi
  renderRow = (item) => <li>{item}</li>;

  render() {
    return <ul>{this.renderRows(this.state)}</ul>;
  }
}

By breaking components down further it allows you to write much simpler tests, and by breaking your handler apart from the axios call you can use test data instead. You're not trying to test your API, right?

In general, you never want to put an asynchronous call in a constructor in Javascript. It's an antipattern because objects you create are in an indeterminate state when the constructor returns.

In React, initialization code that calls asynchronous functions often goes into the componentDidMount lifecycle method.

If you return a promise from componentDidMount, Enzyme will wait for the promise to resolve during rendering.

Of course, you will want to mock out axios so that there are no network calls during your tests. You can do that by calling jest.mock('axios') in the test file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM