2

I've been developing in React for a while for my work, but recently I was requested to get some applications to ~100% test coverage using Istanbul. I've wrote over 160 tests for this application alone in the past few days, but I haven't been able to cover certain parts of my code. Im having the most trouble covering AJAX calls, setTimeout callbacks, and component methods that require another component to operate properly. I've read several SO questions to no avail, and I believe that is because I'm approaching this incorrectly. I am using Enzyme, Chai assertions, Mocha, Istanbul coverage, sinon for spies, and was considering nock since I cant get sinon fakeServer working.

Here is the component method in question:

_getCategoriesFromServer() {
const _this = this;
sdk.getJSON(_this.props.sdkPath, {
    itemsperpage: 10000
}).done(function(data) {
    _this.setState({
        isLoaded: true,
        categories: _this.state.categories.concat(data)
    });
});

}

Here is the test for that component:

 it('should call _getCategoriesFromServer', () => {
    sinon.spy(CategoryTree.prototype, '_getCategoriesFromServer');
    wrapper = mount(<CategoryTree {...props} />);
    expect(CategoryTree.prototype._getCategoriesFromServer.calledOnce).to.be.true;
});

The sdk is just a module that constructs a jQuery API call using getJSON. My test is covering the function call, but its not covering the .done callback seen here: enter image description here So my question is, how can I properly test the .done? If anyone has an article, tutorial, video, anything that explains how to properly test component methods, I would really appreciate it!

Second question is, how can I go about testing a method that gets passed down as a prop to a child component? With the testing coverage requirement I have to have that method tested, but its only purpose is to get passed down to a child component to be used as an onClick. Which is fine, but that onClick is dependent on another AJAX call returning data IN the child component. My initial impulse was to just use enzymes .find to locate that onClick and simulate a click event, but the element with that onClick isn't there because the AJAX call didn't bring back data in the testing environment. If you've read this far, I salute you. And if you can help, I thank you!

Munsterberg
  • 758
  • 4
  • 17
  • 37
  • 1
    Testing that `componentDidMount` is pointless; that's re-testing React and your test framework. The first part of testing `getJSON` is stubbing and making sure it's called with the correct arguments. You'd return an object with a `done` function and make sure that the state is set properly, but this introduces an issue since the way it's coded it's calling `setState`, which is asynchronous. – Dave Newton Jan 13 '17 at 00:58
  • @DaveNewton Sorry that was actually the wrong test. But I will take that componentDidMount test out. Can sinon go about accomplishing that? – Munsterberg Jan 13 '17 at 01:06
  • @DaveNewton and also, wouldn't using a stub cause the original function not to run, therefore not getting that test coverage? – Munsterberg Jan 13 '17 at 01:13
  • I think you need something like https://github.com/nodejitsu/mock-request ,maybe you need to mock your sdk, prepare different cases and check state change match your expect after a while( in a setTimeout?) – Josh Lin Jan 16 '17 at 09:25

1 Answers1

5

You could use rewire(https://github.com/jhnns/rewire) to test your component like this:

// let's said your component is ListBox.js
var rewire = require("rewire");
var myComponent = rewire("../components/ListBox.js");

const onDone = sinon.spy()
const sdkMock = {
    getJSON (uri, data) {
       return this.call('get', uri, data);
    },
    call: function (method, uri, data) {
       return { done: function(){ onDone() } }
    }
};
myComponent.__set__("sdk", sdkMock);

and finally you will test if the done function get called like this:

expect(onDone.calledOnce)to.be.true

With this should work as expected. If you need more options you could see all the options of rewire in GitHub.

BABEL

If you are using babel as transpiler you need to use babel-plugin-rewire(https://github.com/speedskater/babel-plugin-rewire) you could use it like this:

sdk.js

function call(method, uri, data) {
   return fetch(method, uri, data);
}
export function getJSON(uri, data) {
   return this.call('get', uri, data);
}

yourTest.js

import { getJSON, __RewireAPI__ as sdkMockAPI } from 'sdk.js';

describe('api call mocking', function() {
   it('should use the mocked api function', function(done) {
      const onDone = sinon.spy()
      sdkMockAPI.__Rewire__('call', function() {
         return { done: function(){ onDone() } }
      });
      getJSON('../dummy.json',{ data: 'dummy data'}).done()
      expect(onDone.calledOnce)to.be.true
      sdkMockAPI.__ResetDependency__('call')
   })
})
damianfabian
  • 1,681
  • 12
  • 16
  • This looks like a solution but im having issues with the last line in your top example. This is what my sdk module looks like: `module.exports = { getJSON (uri, data) { return this.call('get', uri, data); }, ` gist of entire module: https://gist.github.com/Munsterberg/be8165312ab0c69284f72cae72de37b9 – Munsterberg Jan 16 '17 at 16:35
  • I see, in that case It will be easier just rewire your call function, I will update my answer – damianfabian Jan 16 '17 at 16:40
  • When I require my module with `rewire(path)` it breaks my other component tests, is that intended behaviour? – Munsterberg Jan 16 '17 at 16:58
  • 1
    It has some limitations with ES6, I just update my answer to show you the same functionality with babel and ES6 syntax. – damianfabian Jan 17 '17 at 05:06