2

I'm working with create-react-app, jest and enzyme.

I have a class component that loads "detail" data when a table row is clicked, then displays a form in a modal where the details can be edited and saved:

import React, { Component } from "react";
import fetch from "isomorphic-fetch";
import Modal from "./Modal";

export default class RecordViewer extends Component {
  constructor(props) {
    super(props);
    this.state = {
        showModal: false,
        currentRecord: {}
    };
  }

  open(id) {
    fetch(`api/${id}`)
      .then(res => res.json())
      .then(data => {
        this.setState({
          ...this.state,
          currentRecord: data.record,
          showModal: true
        });
      });
  }

  render() {
    <div>
      <Modal visible={this.state.showModal} contents={this.state.currentRecord} />
      <table>
        <tr onClick={()=>this.open(1)}>
          <td>Record Overview for Item 1</td>
        </tr>
      </table>
    </div>
  }
}

I want to mock the fetch function to determine if it is called or not, but also just to keep the component from trying to hit the API. I want to test the contents passed to the modal, for example.

Passing fetch in as a prop would make this easy but then this component's parent would need to know about it, which doesn't seem to make sense.

Is there a way to mock fetch with this set up?

Perhaps there is a better way to go about testing this altogether?

user6689821
  • 147
  • 11

2 Answers2

2

There is a way but it's not ideal. Have a look at the fetch-mock package.

The reason why it's not ideal is that you will not be totally isolating your component so technically it wouldn't be a unit test any more but you didn't specify what kind of test you're writing.

ivarni
  • 17,658
  • 17
  • 76
  • 92
  • I am writing unit tests. I should have said! Thanks for the package, it looks super useful. I'm thinking a refactor is what's called for in my current case. – user6689821 May 30 '17 at 07:24
2

Actually you should create like a "container" component to connect business rules and "view" ex;

src/components
  -> RecordViewer.js
  -> RecordViewerContainer.js

So, in the RecordViewer.js you can do like a PURE component wich just export a callback functions ex.

RecordViewer.js

import React, { Component } from "react";
import Modal from "./Modal";

class RecordViewer extends Component {
  render() {
    <div>
      <Modal visible={this.props.showModal}
       contents={this.props.currentRecord} />
      <table>
        <tr onClick={() => this.props.onOpen(1)}>
          <td>Record Overview for Item 1</td>
        </tr>
      </table>
    </div>
  }
}

RecordViewer.propTypes = {
  showModal: React.PropTypes.bool,
  onOpen: React.PropTypes.func,
  currentRecord: React.PropTypes.object
}

RecordViewer.defaultProps = {
  showModal: false,
  currentRecord: {},
  onOpen: function(){}
}

export default RecordViewer;

RecordViewerContainer.js

import React, { Component } from "react";
import fetch from "isomorphic-fetch";
import RecordViewer from "./RecordViewer";

export default class RecordViewerContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
        showModal: false,
        currentRecord: {}
    };
  }

  open(id) {
    fetch(`api/${id}`)
      .then(res => res.json())
      .then(data => {
        this.setState({
          ...this.state,
          currentRecord: data.record,
          showModal: true
        });
      });
  }

  render() {
    <RecordViewer currentRecord={this.state.currentRecord} showModal={this.state.showModal} />
  }
}

Then you can mock the component params and separate the API calls and business rules.

tip: We have tools wich do this better, like redux, altjs, reflux (flux implementations).

Bruno Agutoli
  • 200
  • 3
  • 8
  • This component _is_ a redux container, actually, I just simplified it for my question. Looks like it needs a little refactoring, anyway! I was a little unclear about what local state should hold vs. redux store, but with a little research [I found this](https://github.com/reactjs/redux/issues/1287). RecordViewer will hold form state, so it might make sense to keep it local? Anyway, thanks for helping me think this through! – user6689821 May 30 '17 at 07:21