When a method is called with an event listener in a React component, the original method is always called and not the mocked one.
// component.jsx
/** @jsx React.DOM */
'use strict';
var React = require('react/addons');
var MyComponent = React.createClass({
getInitialState: function() {
return {
clicked: false
};
},
onButtonClick: function() {
this.setState({
clicked: true
});
},
render: function() {
return (
<button onClick={this.onButtonClick}>My Button</button>
);
}
});
module.exports = MyComponent;
// __tests__/component-test.js
'use strict';
jest.dontMock('../component.jsx');
describe('Component', function() {
it('must call onButtonClick on click', function() {
var React = require('react/addons');
var MyComponent = require('../component.jsx');
var TestUtils = React.addons.TestUtils;
var myComponent = TestUtils.renderIntoDocument(<MyComponent />);
myComponent.onButtonClick = jest.genMockFunction();
var button = TestUtils.findRenderedDOMComponentWithTag(myComponent, 'button');
TestUtils.Simulate.click(button);
expect(myComponent.onButtonClick.mock.calls.length).toBe(1);
expect(myComponent.state.clicked).toEqual(false);
});
});
Output:
$ npm test
> [email protected] test /Users/ycroissant/Workspace/jest-bug
> jest
Using Jest CLI v0.2.1
FAIL __tests__/component-test.js (0.566s)
● Component › it must call onButtonClick on click
- Expected: 0 toBe: 1
at Spec.<anonymous> (/Users/ycroissant/Workspace/jest-bug/__tests__/component-test.js:19:53)
at Timer.listOnTimeout [as ontimeout] (timers.js:112:15)
- Expected: true toEqual: false
at Spec.<anonymous> (/Users/ycroissant/Workspace/jest-bug/__tests__/component-test.js:20:37)
at Timer.listOnTimeout [as ontimeout] (timers.js:112:15)
1 test failed, 1 test passed (2 total)
Run time: 4.614s
npm ERR! Test failed. See above for more details.
In this example the mock were not called and the original method updated the state. But it works if I call directly myComponent.onButtonClick (the mock is called and not the original method).
Facing the same issue...
25 days and still no solution?
Because of the custom internals of React.createClass, this is pretty tricky to solve. We've been thinking about how to make testing React components much easier, and that was one of the many reasons behind embracing standard ES6 class syntax that we just released as 0.13.0-beta. If you write your classes in ES6 form, Jest will be able to mock them correctly.
Is there any workaround for this issue? I'm running React 0.13.0-beta.1 and things still don't see to be working when I attempt to mock a method on my component instance.
EDIT Things seem to be working now that I switched my class to ES6 style (and moved by defaultProps, and converted getInitialState to constructor). This poses a potential problem though as mixins cannot be used in ES6 classes like they can in createClass.
@NickPresta could you maybe post an example that is working for you? i switched to 0.13.0-beta.1 as well, but the original methods are still being called instead of the mocks for me.
@leebyron or could you maybe provide a short example?
one way of working around this issue if you don't want to refactor everything would be like this
var MyComponent = React.createClass({
getInitialState: function() {
return {
clicked: false
};
},
onButtonClick: function() {
this.handleButtonClick();
},
handleButtonClick: function(){
this.setState({
clicked: true
});
}
render: function() {
return (
<button onClick={this.onButtonClick}>My Button</button>
);
}
});
Then in your test
var myComponent = TestUtils.renderIntoDocument(<MyComponent />);
myComponent.handleButtonClick = jest.genMockFunction();
Confirmed. If the actual onClick event delegates to another method, genMockFunction() works as expected.
/** @jsx React.DOM */
//tests/app/modules/autocompletesearchinput-test.js
jest.dontMock('../../../app/modules/AutocompleteSearchInput.jsx');
var React, TestUtils, FluxxorTestUtils, FluxConstructor, realFlux, fakeFlux, MyComponent, Component;
describe('Testing Autocomplete Search Input', function() {
beforeEach(function() {
React = require('react/addons');
TestUtils = React.addons.TestUtils;
FluxxorTestUtils = require('fluxxor-test-utils');
FluxConstructor = require('../../../app/FluxConstructor.js');
realFlux = FluxConstructor();
fakeFlux = FluxxorTestUtils.fakeFlux(realFlux);
fakeFlux.genMocksForStoresAndActions();
// now all stores and action methods are mocked for testing
MyComponent = require('../../../app/modules/AutocompleteSearchInput.jsx');
Component = TestUtils.renderIntoDocument(<MyComponent flux={fakeFlux} />);
});
it('should have AutocompleteSearchInput class', function() {
var div = TestUtils.findRenderedDOMComponentWithClass(
Component, 'AutocompleteSearchInput');
});
it('when mounted and clicked, should call searchBtnClick', function() {
Component.chooseSuggestion = jest.genMockFunction();
TestUtils.Simulate.click(Component.refs.searchButton.getDOMNode());
expect(Component.chooseSuggestion).toBeCalled();
});
});
<button
onClick={this.searchBtnClick}
className={searchSubmitClasses}
ref="searchButton">
+1 @grahamfowles. thanks.
Had the same issue, but I don't feel like making a workaround in the code just to allow testing, cause it reduces readability. Isn't there a way to do the workaround only on the test, like injecting a new onClick in the button?
Any update on this issue? :confused:
In the end I also got it to work using ES6 classes. Unless you want to or have to stick to React.createClass, that would be the best/easiest solution, especially now that React v0.13 is out.
For people that are still going down this road, I suggest that you save yourself some time and start using Mocha, Mockery and Sinon to fully mock and spy on your components and their functions. Until Jest solves these problems, I think you will ultimately end up wanting to try something else. We had a lot of success with Mocha, Mockery, Chai and Sinon in our customized test suite. We now seamlessly run tests on React components, Flux Actions and all of our other components (including react-router components!). We run about 100 tests in less than 10 seconds. Here's a working example of how we run these. https://github.com/adjavaherian/mocha-react
@codejet I am failing to get it working with ES6 classes. I tried both assigning to the "class" and to the object:
LoginForm.handleSubmit = jest.genMockFunction();
const loginForm = TestUtils.renderIntoDocument(
<LoginForm />
);
loginForm.handleSubmit = jest.genMockFunction();
I would love to see your code snippets.
@zhon you can find my code here: https://github.com/codejet/google-books-search/tree/reactive-element
I think
LoginForm.prototype.handleSubmit = jest.genMockFunction();
should work. We don't have plans to change how createClass works here, but you should be able to mock plain-class functions like this without trouble.
It just does not work, sooo frustrating. This should be easy.
@juliankrispel Like what @spicyj mention, this is how i got it to work. Hope it helps.
Code:
export default class YearField extends React.Component {
render() {
return (
<input type='text' onChange={this.handleAction} ref='field'/>
);
}
handleAction() { ... }
}
Test:
jest.dontMock(componentPath('YearField'));
const React = require('react/addons');
const TestUtils = React.addons.TestUtils;
const YearField = require('./YearField');
describe('YearField', () => {
YearField.prototype.handleAction = jest.genMockFunction();
let component = TestUtils.renderIntoDocument(element);
it('check change event', () => {
TestUtils.Simulate.change(component.refs.field);
expect(YearField.prototype.handleAction).toBeCalled();
});
});
@clouddra that might be the case for es6 classes ...
so basically if you're still using createClass because you haven't had a chance to refactor your entire codebase to es6 yet, this is what I found:
Component.prototype.__reactAutoBindMap, I had to read the source for this :-1:it 'should fire autosize', ->
InputComponent.prototype.__reactAutoBindMap.autosize = jest.genMockFunction()
component = TestUtils.renderIntoDocument(<InputComponent value={'hello'}/>)
component.onChange({target: {value: 'Yoyoyo'}})
expect(InputComponent.prototype.__reactAutoBindMap.autosize).toBeCalled()
@yannickcr I am trying to do it your way, tests failing :(
Has anyone found a workaround for this issue ?
@spicyj Can you please post an example of how you did it ?
I am also looking for a solution to this. However, I am using straight Jasmine, not Jest.
Using ES6 classes I had to use the solution provided by clouddra. E.g. using MyClass.prototype. Is that the recommended approach or is there still a fix coming here?
Yes, the recommended solution is to do MyClass.prototype.myFn = jest.fn().
Most helpful comment
Yes, the recommended solution is to do
MyClass.prototype.myFn = jest.fn().