22

I'm trying to stub a React component method for testing purpose:

var Comp = React.createClass({
  displayName: "Comp",

  plop: function() {
    console.log("plop");
  },

  render: function() {
    this.plop();
    return React.DOM.div(null, "foo");
  }
});

var stub = sinon.stub(Comp.type.prototype, "plop");
React.addons.TestUtils.renderIntoDocument(Comp());
sinon.assert.called(stub); // throws

This sadly keeps printing "plop" onto the console… and the assertion fails.

Note: Directly stubbing the spec object method works, but then you have to export the component constructor and the spec separately so they're both available in tests… Also, you'd need to stub the spec before even creating the component class; not so convenient:

var CompSpec = {
  displayName: "Comp",

  plop: function() {
    console.log("plop");
  },

  render: function() {
    this.plop();
    return React.DOM.div("foo");
  }
};

var stub = sinon.stub(CompSpec, "plop");
var Comp = React.createClass(CompSpec);
React.addons.TestUtils.renderIntoDocument(Comp());

// plop() is properly stubbed, so you can
sinon.assert.called(stub); // pass

Can you think of another strategy to easily stub a React component method?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
NiKo
  • 11,215
  • 6
  • 46
  • 56

2 Answers2

29

You're running up against React's auto-binding feature, which caches the .bind(this) which is wrapped around your class methods. You can get your code to work by stubbing the cached version of the method in React's __reactAutoBindMap:

var Comp = React.createClass({
  displayName: "Comp",

  plop: function() {
    console.log("plop");
  },

  render: function() {
    this.plop();
    return React.DOM.div(null, "foo");
  }
});

// with older versions of React, you may need to use
// Comp.type.prototype instead of Comp.prototype
var stub = sinon.stub(Comp.prototype.__reactAutoBindMap, "plop");  // <--
React.addons.TestUtils.renderIntoDocument(React.createElement(Comp));
sinon.assert.called(stub);  // passes
danvk
  • 15,863
  • 5
  • 72
  • 116
  • per http://stackoverflow.com/questions/8825870/sinon-js-attempted-to-wrap-ajax-which-is-already-wrapped I then need to restore the wrapped functions if I want to wrap them again in another test. The answer there isn't working. How would you do this using your solution here? – YPCrumble Mar 04 '15 at 17:15
  • @YPCrumble - you could assign the original function to a variable in the beforeEach, then do the stub as above and then reset the original function to the variable you assigned it to in the beforeEach. var originalFunc; beforeEach(function() { originalFunc = Comp.type.prototype.__reactAutoBindMap.plop; var stub = sinon.stub(Comp.type.prototype.__reactAutoBindMap, "plop"); React.addons.TestUtils.renderIntoDocument(React.createElement(Comp)); sinon.assert.called(stub); // passes }); afterEach(function() { Comp.type.prototype.__reactAutoBindMap.plop = originalFunc; }); – alengel Jul 24 '15 at 11:08
  • 1
    @danvk I believe this is deprecated now with React v15 - any suggestions on what you use going forward? – YPCrumble Apr 24 '16 at 21:51
  • 1
    @YPCrumble you could use an ES6 class instead of `React.createClass`. This doesn't do auto-binding. – danvk Apr 25 '16 at 13:38
  • I just attempted to stub `componentDidMount` with React `v15.0.2` and what I did was move the contents of `componentDidMount` into a custom module (not a React class, just a vanilla JS class). In my case, this was `XMLHttpRequest` usage. Then, in my test, I used `sinon.stub()` on my module's method, which is called inside `componentDidMount`. This allowed the test to pass. I personally don't see much value in stubbing/mocking `XMLHttpRequest` in this case, but the catch I guess is less than 100% coverage. – Mike Moore Jun 04 '16 at 14:21
5

Which test framework are you using?

If you use jasmine, I've found jasmine-react to be a useful library for spying on React methods as well as replacing Components with test stubs.

In this case, you can spy on your method easily outside component definition.

//Component Definition
var Comp = React.createClass({

    displayName: "Comp",

    plop: function() {
       console.log("plop");
    },

    render: function() {
       this.plop();
       return React.DOM.div(null, "foo");
    }
});

//test
it("should call plop method on render", function(){
   //spy on method
   jasmineReact.spyOnClass(Comp, "plop");
   React.addons.TestUtils.renderIntoDocument(Comp());
   expect(Comp.plop).toHaveBeenCalled();
})

jasmineReact.spyOnClass returns an ordinary jasmine spy that you can use to track calls to it and its arguments.

If you want to actually stub the method and make it return something, you can do something like jasmineReact.spyOnClass(Comp, "plop").andReturn('something')

Alternatively Facebook have recently launched a test framework Jest (also has jasmine as a dependency) which they use themselves for testing React components. Component methods can easily be stubbed using this framework. This looks like its worth checking out as well, but probably comes into its own a bit more when you write your components inside commonJS modules

Ron
  • 1,875
  • 1
  • 18
  • 14