Unit testing React component using Material UI Dialog

jonas.hartwig picture jonas.hartwig · Dec 16, 2015 · Viewed 9.7k times · Source

I am currently writing unit tests for my React + MaterialUi application.

In my application I have a Dialog. I want to make sure depending on what button pressed on the dialog:

<FlatButton
  label="Cancel"
  secondary={true}
  onTouchTap={this._cancelDialog.bind(this)} 
 />
 <FlatButton
   label="Submit"
   primary={true}
   onTouchTap={this._confirmDialog.bind(this)} 
 />

that the internal state changes accordingly.

Unfortunately i cannot get ahold of the dialog content using TestUtils.scryRenderedComponentsWithType(FlatButton)
or
scryRenderedComponentsWithTag("button")
and so on.

Any ideas on how that flow can be tested?

Update 1

So I can get the Dialog instance by calling TestUtils.scryRenderedComponentsWithType(Dialog). But I can not get the dialogs content. DOM wise the content does not render inside the view itself. Its rendered in a new created node on document level (div). So i tried this:

let cancelButton = window.document.getElementsByTagName("button")[0];
Simulate.click(cancelButton);

cancelButton in the case above is the correct DOM element. Simulate.click however does not trigger the components click function.

regards Jonas

Answer

avocadojesus picture avocadojesus · Feb 1, 2016

just ran into the same problem. I looked into the source code, and the Dialog component's render method actually creates an instance of the component RenderToLayer. this component behaves as a portal and breaks react's DOM tree by returning null in its' render function and instead appending directly to the body.

Luckily, the RenderToLayer component accepts the prop render, which essentially allows the component to pass to the portal a function to be called when it is in a render cycle. This means that we can actually manually trigger this event ourselves. It's not perfect, i admit, but after a few days of poking around trying to find a solution for this hack i am throwing in the towel and writing my tests like this:

var component = TestUtils.renderIntoDocument(<UserInteractions.signupDialog show={true}/>)
var dialog = TestUtils.renderIntoDocument(component.refs.dialog.renderLayer())
var node = React.findDOMNode(dialog)

and here is what my UserInteractions.signupDialog looks like:

exports.signupDialog = React.createClass({
...
  render: function() {
    var self = this;

    return (
      <div>
        <Dialog
          ref='dialog'
          title="Signup"
          modal={false}
          actions={[
            <Button
              label="Cancel"
              secondary={true}
              onTouchTap={self.__handleClose}
            />,
            <Button
              label="Submit"
              primary={true}
              keyboardFocused={true}
              onTouchTap={self.__handleClose}
            />
          ]}
          open={self.props.show}
          onRequestClose={self.__handleClose}
          >
          <div className='tester'>ham</div>
          <TextField id='tmp-email-input' hintText='email' type='text'/>
        </Dialog>
      </div>
    )
  }
})

Now i can make assertions against the child components rendered in the dialog box, and can even make assertions about events bound to my original component, as their relationship is maintained.

I definitely recommend setting up a debugger in your testing stack if you are going to continue using material ui. Theres not a lot of help for things like this. Heres what my debug script looks like:

// package.json
{
  ...
  "scripts": {
    "test": "mocha --compilers .:./test/utils/compiler.js test/**/*.spec.js",
    "debug": "mocha debug --compilers .:./test/utils/compiler.js test/**/*.spec.js"
  }
}

and now you can use npm test to run mocha tests, and npm run debug to enter debugger. Once in the debugger, it will immediately pause and wait for you to enter breakpoints. At this juncture, enter c to continue. Now you can place debugger; statements anywhere in your code to generate a breakpoint which the debugger will respond to. Once it has located your breakpoint, it will pause and allow you to engage your code using local scope. At this point, enter repl to enter your code's local scope and access your local vars.

Perhaps you didnt need a debugger, but maybe someone else will find this helpful. Good luck, happy coding!