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

How to mock React components? #18

Open
eguneys opened this issue Jul 17, 2015 · 15 comments
Open

How to mock React components? #18

eguneys opened this issue Jul 17, 2015 · 15 comments

Comments

@eguneys
Copy link

eguneys commented Jul 17, 2015

I can't figure out a way to mock React components.

Rewire approach doesn't work: jhnns/rewire-webpack#12
alternative doesn't work speedskater/babel-plugin-rewire#16
Sinon doesn't work it throws some errors when I do import 'sinon'

I don't know how to setup Jest.

Can you provide an example how to mock components in this setup?

Also this: http://stackoverflow.com/questions/31475048/how-to-isolate-and-iterate-over-individual-react-components-in-a-react-applicati

@bulkan
Copy link

bulkan commented Jul 17, 2015

I can include sinon via this config for webpack in my karma.conf

      plugins: [
        new webpack.NormalModuleReplacementPlugin(/^sinon$/,  __dirname + '/sinon-1.15.4.js')
      ],

      module: {
        noParse: [
          /sinon/
      ],

make sure to download sinon

that being said, I can't mock anything because all my react components are created before I get a chance to mock martyjs

@speedskater
Copy link

@eguneys a new version of babel-plugin-rewire is available which should fix the latest bugs. Could you please let me know whether your errors still persist.

@bultas
Copy link

bultas commented Jul 19, 2015

i use "sinon": "git://github.com/cjohansen/Sinon.JS#sinon-2.0" and no problem with similar setup with: babel, babel rewire plugin, karma, mocha...

badsyntax added a commit that referenced this issue Jul 21, 2015
@badsyntax
Copy link
Owner

I initially went with Jest and it worked pretty well, but I encountered node.js compatibility issues as a result of depending on the JSDom package which is why I ended up the Mocha route.

In any case, have a look at 7a5fc1c I've added a mocking component example using the babel-plugin-rewire package.

@eguneys
Copy link
Author

eguneys commented Jul 22, 2015

I have a higher order component:

import React from 'react';

function withMUI(ComposedComponent) {
  return class withMUI {
    render() {
      return <ComposedComponent {...this.props}/>;
    }
  };
}

and a component:

import React from 'react';
import withMUI from 'decorators/withMUI';

@withMUI
class PlayerProfile extends React.Component {
  render() {
    const { name, avatar } = this.props;
    return (
      <div className="player-profile">
        <div className='profile-name'>{name}</div>
        <div>
          <img src={avatar}/>
        </div>
      </div>
    );
  }
}

and a (passing) test using React.findDOMNode

import React from 'react/addons';

describe('PlayerProfile', () => {
  let { TestUtils } = React.addons.TestUtils;
  // profile is type of `withMUI`
  let profile = TestUtils.renderIntoDocument(<OkeyPlayerProfile/>);

  it('should work', () => {
    let elem = React.findDOMNode(profile);

    // logs successfully
    console.log(elem.querySelectorAll('.player-profile'));
  });

  // ...
});

and another (failing) test using TestUtils:

   // ...
   it('should also work', () => {
     let elem = TestUtils.findComponentWithTag(profile, 'div');
     // throws can't find a match
     console.log(elem);
   });

If I remove the @withMUI decorator it works as expected. So why does a decorator effect TestUtils.findComponentWithTag and how can I make this work?

How can I mock withMUI function? using babel-plugin-rewire. or rewire?

@badsyntax
Copy link
Owner

I'm unsure if babel-plugin-rewire or rewire would be able to mock that. TBH i'm not exactly familiar with either so I'm trying to figure this out myself. Could you update your examples to include all the require calls? It just helps me understand your scenario better.

When I have some time I'll look into this.

@speedskater
Copy link

@badsyntax Imho to be able to mock withMUI with babel-plugin-rewire you have to export a factory which returns the component instead of the component itself. The reason is that the returned component has already got withMUI applied.

@eguneys
Copy link
Author

eguneys commented Jul 23, 2015

@speedskater The reason I am mocking withMUI is to get rid of the wrapper, I can't solve that by adding another wrapper lol.

@speedskater
Copy link

@eguneys Good point :)

@badsyntax
Copy link
Owner

@speedskater can you give an example of how to do this?

I've been having some troubles understanding how to do this at all. In my component file, I define the decorator like so:

var withMUI = function(ComposedComponent) {
  //...
}

@withMUI
export default class MenuItem extends Component {
  //...
}

I've tried to mock both the decorator and the class but I can't remove the decorator from the exported class. I've tried the following:

function mockedWithMUI(ComposedComponent) {
  return class withMUI {
    render() {
      //...
    }
  };
}

MenuItem.__Rewire__('withMUI', mockedWithMUI);

@mockedWithMUI
class MockedMenuItem extends MenuItem {

}

Menu.__Rewire__('MenuItem', MockedMenuItem);

..but all that does is add another decorator onto the class instead of replacing the withMUI decorator..

@speedskater
Copy link

@badsyntax As far as i have understand you cannot test your component in this way. The reason is, that - as you have already recognized - you have already decorated your class at import time and mocking the decorator at this point in time won't change your class's behaviour. Your only change is to use a factory function

export function createDecoratedComponent() {
return @withMU,,,,
}

and mock the decorator @withMUI before actually calling createDecoratedComponent.
The drawback in this case is that you generate a new class definition each time you use the module.

Another possibility would be to separate the definition of the plain MenuItem Component from the decorated component. This would allow you to test them separately.
I am not an expert in using decorators but as far as i have understood decorators are only functions which convert classes into other classes. Therefore it should be possible to create two separate files.

MenuItem.js with the undecorated component and
MenuItemDecorated.js which calls the decorator explicitly on the imported MenuItem class

Hope this answer is not too confusing and helps you to test your component.

@badsyntax
Copy link
Owner

@speedskater Apologies for the belated response. Yes that makes sense, it's awkward though, i'm sure you'll agree. We don't really want to call factory functions for generating class definitions.
I was hoping there was some way to remove a decorator from a class definition. (Any suggestions?) Alas I'm not familiar enough with decorators and the transpliation process to understand a better approach here.

@badsyntax
Copy link
Owner

Sorry I didn't finish reading the entire comment.

MenuItem.js with the undecorated component and
MenuItemDecorated.js which calls the decorator explicitly on the imported MenuItem class

This seems like the best approach IMO. @eguneys does that work for you?

@badsyntax badsyntax reopened this Jul 27, 2015
@dtothefp
Copy link

@eguneys see my answer here on your post https://stackoverflow.com/questions/31459720/how-to-test-decorated-react-component-with-shallow-rendering/32194659#32194659.

I'm having the same problem testing decorators. You pretty much have to wrap your component in another higher order component/decorator in your tests, and have that higher-order-component/decorator provide the state/props/context that withMui expects. Seems ugly and not dry but I'm not sure of another way?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants