Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing React component using Material UI Dialog

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

like image 409
jonas.hartwig Avatar asked Dec 15 '22 10:12

jonas.hartwig


2 Answers

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!

like image 122
avocadojesus Avatar answered Dec 16 '22 23:12

avocadojesus


Solved it as follows:

/*
* I want to verify that when i click on cancel button my showModal state is set * to false
*/

//shallow render my component having Dialog
const wrapper= shallow(<MyComponent store={store} />).dive();

//Set showModal state to true
wrapper.setState({showModal:true});

//find out cancel button with id 'cancelBtn' object from actions and call onTouchTap to mimic button click
wrapper.find('Dialog').props().actions.find((elem)=>(elem.props.id=='cancelBtn')).props.onTouchTap();

//verify that the showModal state is set to false
expect(wrapper.state('showModal')).toBe(false);
like image 24
gsingh Avatar answered Dec 17 '22 00:12

gsingh