Enzyme: Refs not working in component being shallow rendered

Created on 11 Apr 2016  路  9Comments  路  Source: enzymejs/enzyme

Hello,

I am trying to shallow render a component and test it using enzyme. My component looks like this:

class LogIn extends Component {
  static propTypes = {
    login: PropTypes.func
  };

  handleSubmit = () => {
    const email = this.refs.email;
    this.props.login(email.value);
  };
  render() {
    return (
      <div>
        <input type="text" ref="email" />
        <button onClick={this.handleSubmit}>Click me!</button>
      </div>
    );
  }
}

Inside one of my tests with karma and enzyme, I run the following line:

buttons.at(0).simulate('click'); // This is the button element

This results in this error:

TypeError: Cannot read property 'value' of undefined

Basically the this.refs are undefined. I can't seem to get the refs to work inside the component that I'm testing. Is this even possible with enzyme?

Most helpful comment

You might have noticed the ShallowRenderer doesn't have docs for a ref() mehthod, while the MounedRenderer does. If you want to test refs you have to mount.

I believe the reason is that shallow rendering does not maintain an internal instance and therefore it can't hold a ref. That is the purpose of shallow rendering. Even FB ReactTestUtils shallowRendering doesn't work with refs.

All 9 comments

You might have noticed the ShallowRenderer doesn't have docs for a ref() mehthod, while the MounedRenderer does. If you want to test refs you have to mount.

I believe the reason is that shallow rendering does not maintain an internal instance and therefore it can't hold a ref. That is the purpose of shallow rendering. Even FB ReactTestUtils shallowRendering doesn't work with refs.

@blainekasten Thank you! That cleared it up for me. I'm using mount() now. I have one other unrelated question, is it possible to simulate the entry of text on an uncontrolled input with enzyme? For example:

component.find('.email').at(0).simulate('keypress', { which: 'a' })

When I do this on an input like this:

<input type="text" ref="email" placeholder="Email" className="email" />

I am unable to successfully simulate the typing. Is this even possible with an uncontrolled input? Let me know if I should create a new issue for this. Thank you!

I haven't looked into the simulate code in a while. But I'm pretty sure that just triggers the internal React event system. Since that component is uncontrolled it wouldn't be registered in reacts event system.

I think you would want to do a full fledge Event in this case. Something like:

const e = new Event('keyboard');
// setup the event
input.dispatchEvent(e);

@blainekasten Thanks for the response! I've been trying to implement this, here's what I've got so far:

const input = inputs.at(0);
const evt = new Event('keyboard');
evt.initKeyEvent ('keypress', true, true, (new Object()),
                  0, 0, 0, 0,
                  0, 'e'.charCodeAt(0));
const canceled = !input.dispatchEvent(evt);

if (canceled) {
  console.log('does not work');
} else {
  console.log('works');
}

inputs comes from component.find('input') using enzyme, and inputs.at(0) maps to a text input. I am getting this error: evt.initKeyEvent is not a function.

I just can't seem to get it to work.

I think you are nearly there but not quite. The problem is with:

const input = inputs.at(0)

You are wanting to trigger the event on the actual DOM node. at() returns an EnzymeWrapper. So two things you'll have to do.

  1. Make sure you are using mount and not shallow for this test.
  2. Make sure you are getting the DOM node. You might have to do some wrapper digging.

Untested, But I think this is the code change you want.

const wrapper = shallow(<MyElement />);
const input = wrapper.find('input').at(0).node; // node is the actual DOM node

// event stuff....

input.dispatchEvent(evt);

@blainekasten I ended up using redux-form to make the inputs controlled and then just simulated the form submit, but I will certainly be trying this on my tests in the future. Thank you!

@blainekasten: Would be much needed to be able to INJECT refs in case of shallow rendering.
This would enable to make all these test possible that reference refs.

I understand why refs are not available in shallow (non-DOM rendering) but why should it be prohibited to test such code? I was trying to inject refs manually without luck so far.

@blainekasten Hi blainekasten , Am trying to render my component using enzyme and mocha . While rendering the child component into parent component using mount(node[, options]) It could not attach the component. Could you please help me on this ? PFA
my component looks like this parent.js

 render() {
    return (        
     <div id="parent">
            <div id="child"></div>
            <div id="childs"></div>
     </div>
 );
}

and test file is parent.spec.js

parentobj = mount(<parentcomp/>);
   var parentfindobj = parentobj.find("#child");
   mount(<childcomp/>,{options: { attachTo: parentfindobj }});

@blainekasten I am trying to test this

bodymovin.loadAnimation({
        container: this.refs.bodyMovinRef, // the dom element that will contain the animation
        renderer: 'svg',
        loop: true,
        autoplay: true,
        animationData: this.props.animationData // the animation json object
    });

render() {
    return (
        <div className = {`${this.props.className} bodyMovin`} ref="bodyMovinRef" />
    );
}

How do I test the ref is being set correctly?

My test looks like this:
describe('BodyMovin', () => {
let bodyMovinMock;

beforeEach(() => {
    jasmineEnzyme();

    bodyMovinMock = {
        loadAnimation: jasmine.createSpy('loadAnimation'),
        destroy: jasmine.createSpy('destroy')
    };

    BodyMovinRewireAPI.__Rewire__('bodymovin', bodyMovinMock);
});

afterEach(() => {
    BodyMovinRewireAPI.__ResetDependency__('bodymovin');
});

fit('loads the animation when the component mounts', () => {
    let component = mount(<BodyMovin animationData="mockAnimationData"/>);
     let bodyMovinClassName = component.find('ref.bodyMovinRef');

    let mockAnimationData = { container: bodyMovinClassName, renderer: 'svg', loop: true, autoplay: true, animationData: 'mockAnimationData' };
    expect(bodyMovinMock.loadAnimation).toHaveBeenCalledWith(mockAnimationData);
    expect(bodyMovinMock.loadAnimation.getDOMNode()).to.have.property('ref');
});

});

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AdamYahid picture AdamYahid  路  3Comments

ivanbtrujillo picture ivanbtrujillo  路  3Comments

modemuser picture modemuser  路  3Comments

blainekasten picture blainekasten  路  3Comments

dschinkel picture dschinkel  路  3Comments