contains method makes very simple testing complex component and don't care about markup. Something like this works great:
const MyComponent = () => (<div><Input /></div>)
it('should render input', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.contains(<Input />)).to.equal(true);
});
But if I have function props, it doesn't work anymore:
class MyComponent extends React.Component {
_onChange() {}
render() {
return <div><Input onChange={this._onChange.bind(this)} /></div>;
}
}
it('should render input', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.contains(<Input onChange={what_should_I_put_here?} />)).to.equal(true);
});
In some cases it is possible to make binding in constructor (then I can get function from instance and pass as prop), but not always.
Do you see any possible solution for this problem? (for example https://github.com/algolia/expect-jsx just skip all function props when compere jsx)
@smacker generally you'd want to do this.onChange = this.onChange.bind(this) in the constructor anyways, to avoid creating a function in the render path.
If you do that, then wrapper().instance().onChange would be the function.
I mentioned that it's possible to move binding in constructor in some cases, but not always.
Consider this examples:
items.map((item) => <Input onChange={this._onInputChange.bind(this, item.type))} />
items.map((item) => <Input onChange={this._makeOnChangeCallback(item.name)} />
Ah, true you did. In that case, you created a new function and didn't store it anywhere else - it doesn't seem possible to locate things based on that function. You'd probably want to spy on the onInputChange etc prior to calling render, and then assert that the prop function calls through to your original.
Could you please give a example with onChange={this._onChange.bind(this, item.type)}?
I ended up with a very ugly code, that overrides bind method :(
My point is, that in some cases I want to find component, but skip some props, i'll tests a behavior in other tests like:
it('should contain input', () => {
expect(wrapper.contains(<Input myProp='I want to check' />)).to.equal(true);
});
it('change of input should set name state', () => {
const newValue = 'newValue';
wrapper.find(Input).props('onChange')(newValue);
expect(wrapper.getState('name')).to.equal(newValue);
})
In this case I don't care about private callbacks that were passed to component, I test only behavior.
So, in this case, I'd do something like the following:
it('should render input', () => {
const wrapper = shallow(<MyComponent />);
const input = wrapper.find(Input);
expect(input).to.have.length(1);
});
Okay, thanks. It will work:
it('should render input', () => {
const wrapper = shallow(<MyComponent />);
const inputs = wrapper.find(Input);
expect(inputs.filter((input) => input.prop('myProp') === 'I want to check').to.have.length(1);
});
but
it('should render input', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.contains(<Input myProp='I want to check' />)).to.equal(true);
});
looks nicer :(
Unfortunately there's just no way to do it cleanly in the case you mentioned (where you are forced to create a function in the render path).
there is.
expect(
wrapper.contains(<Input myProp='I want to check' />, skipProps=['onChange'])
).to.equal(true);
or something like this
hmm, interesting idea. I feel like in that case you might want .contains(Input, subsetOfProps) instead of jsx tho
.contains(Input, subsetOfProps) is okay too. jsx just looks prettier.
Hi guys,
I had exactly the same issue with contains/equals functions, so I wrote some code here to be able to write nicer tests.
https://github.com/mathieuancelin/enzyme/commit/4ee71fade969a7be3f2d90caebb7ce1777991e1c
It's just 2 new functions, rightEquals and rightContains (of course, I can change the names) that will compare the rendered element to the expected element and not the other way around.
You can look at the tests to see how to use it
If you're interested, just let me know.
Most helpful comment
@smacker generally you'd want to do
this.onChange = this.onChange.bind(this)in the constructor anyways, to avoid creating a function in the render path.If you do that, then
wrapper().instance().onChangewould be the function.