Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failing to mock a method called with a event listener in React/JSX #207

Closed
yannickcr opened this issue Jan 5, 2015 · 23 comments
Closed

Comments

@yannickcr
Copy link

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

> jest-bug@1.0.0 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).

@abhinavrastogi
Copy link

Facing the same issue...
25 days and still no solution?

@leebyron
Copy link
Contributor

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.

@nickpresta
Copy link
Contributor

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.

@codejet
Copy link

codejet commented Feb 17, 2015

@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.

@codejet
Copy link

codejet commented Feb 18, 2015

@leebyron or could you maybe provide a short example?

@grahamfowles
Copy link

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();

@adjavaherian
Copy link

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.

@joaomilho
Copy link

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?

@srph
Copy link
Contributor

srph commented Mar 15, 2015

Any update on this issue? 😕

@codejet
Copy link

codejet commented Mar 15, 2015

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.

@adjavaherian
Copy link

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

@zhon
Copy link

zhon commented Apr 6, 2015

@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.

@codejet
Copy link

codejet commented Apr 7, 2015

@sophiebits
Copy link
Contributor

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.

@juliankrispel
Copy link

It just does not work, sooo frustrating. This should be easy.

@clouddra
Copy link

@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();
  });
});

@juliankrispel
Copy link

@clouddra that might be the case for es6 classes ...

@juliankrispel
Copy link

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 methods are bound to Component.prototype.__reactAutoBindMap, I had to read the source for this 👎
  • using that I can finally spy on my component methods, this is how it looks like. (coffeescript sry)
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()

@sukeshni
Copy link

@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 ?

@abobwhite
Copy link

I am also looking for a solution to this. However, I am using straight Jasmine, not Jest.

@benjy
Copy link

benjy commented Aug 3, 2016

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?

@cpojer
Copy link
Member

cpojer commented Aug 3, 2016

Yes, the recommended solution is to do MyClass.prototype.myFn = jest.fn().

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 14, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests