1

I have an App component that is responsible for rendering child input components, it is also responsible for handling fetch requests to the Twitch API via a method called channelSearch. I have tried to adhere to suggested best practices outlined here for working with ajax/fetch with React.

The method is passed down through props and called via a callback.

Note the fetch method is actually isomorphic-fetch.

channelSearch (searchReq, baseUrl="https://api.twitch.tv/kraken/channels/") {
  fetch(baseUrl + searchReq)
  .then(response => {
    return response.json();
  })
  .then(json => {
    this.setState({newChannel:json});
  })
  .then( () => {
    if (!("error" in this.state.newChannel) && this.channelChecker(this.state.newChannel._id, this.state.channelList) ) {
      this.setState(
        {channelList: this.state.channelList.concat([this.state.newChannel])}
      );
    }
  })
  .catch(error => {
    return error;
  });
}

I am currently trying to write a test for the channelSearch method. I am currently using enzyme and jsdom to mount the entire <App> component in a DOM. Find the child node with the callback, simulate a click (which should fire the callback) and check to see if the state of the component has been changed. However, this does not seem to work.

I have also tried calling the method directly, however, I run into problems with this.state being undefined.

test('channel search method should change newChannel state', t => {
  const wrapper = mount(React.createElement(App));

  wrapper.find('input').get(0).value = "test";
  console.log(wrapper.find('input').get(0).value);
  wrapper.find('input').simulate("change");

  wrapper.find('button').simulate("click");

  console.log(wrapper.state(["newChannel"]));


});

I am really lost, I am not sure if the method itself is poorly written or I am not using the correct tools for the job. Any guidance will be greatly appreciated.

Update #1:

I included nock as recommended in comments, test now looks like this:

test('channel search method should change newChannel state', t => {
  // Test object setup

  var twitch = nock('https://api.twitch.tv')
                .log(console.log)
                .get('/kraken/channels/test')
                .reply(200, {
                  _id: '001',
                  name: 'test',
                  game: 'testGame'
                });

  function checker() {
    if(twitch.isDone()) {
      console.log("Done!");
      console.log(wrapper.state(["newChannel"]));
    }
    else {
      checker();
    }
  }

  const wrapper = mount(React.createElement(App));
  wrapper.find('input').get(0).value = "test";
  wrapper.find('input').simulate("change");
  wrapper.find('button').simulate("click");

  checker();
});

This still does not seem to change the state of the component.

2
  • Please include your tests, as they're clearly part of the question :) Commented Apr 21, 2016 at 2:47
  • @markthethomas Added, thanks for the interest! Commented Apr 21, 2016 at 15:08

3 Answers 3

1

fetch is asynchronous but you're testing synchronously, you need to either mock fetch with a synchronous mock or make the test asynchronous.

nock may work for you here.

Sign up to request clarification or add additional context in comments.

1 Comment

I have updates my test to try and attempt to mock fetch with nock as suggested, however, I am still seeing the same issue.
0

I suggest you create a sample of your test using plnkr.

I agree with Tom that you're testing synchronously. It would of course be helpful to show off your actual component code (all of the relevant portions, like what calls channelSearch, or at the least describe it by saying e.g. "channelSearch is called by componentDidMount()". You said:

I run into problems with this.state being undefined.

This is because this.setState() is asynchronous. This is for performance reasons, so that React can batch changes.

I suspect you'll need to change your code that is currently:

.then(json => {
    this.setState({newChannel:json});
})

to:

.then(json => {
  return new Promise(function(resolve, reject) {
    this.setState({newChannel:json}, resolve);
  })
})

Note that your checker() method won't work. It's looping, but twitch.isDone() will never be true because it never has a chance to run. Javascript is single threaded, so your checker code will run continuously, not allowing anything else in between.

If you set up the plnkr, I'll take a look.

Comments

0

Refactor out the fetch code from the component then pass it it to the component as a callback function in the properties.

export class Channel extends React.Component {
  componentDidMount() {
    this.props.searchFunction().then(data => this.setState(data));
  }

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

Uage:

function channelSearch(name) {
  return fetch(`https://api.twitch.tv/kraken/search/channels?query=${name}`);
}

<Channel searchFunction={channelSearch} />

Now you can test the API functionality independently of the component.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.