To set the value of an input currently I have to do:
const form = mount(<MyComponent />);
const input = form.find('input').get(0);
input.value = 'Blah blah';
It would be really nice in my opinion if I could instead do something akin to jQuery:
form.find('input').text('Blah blah')
// or probably better
form.find('input').value('Blah blah')
What are your thoughts on that? I'd be happy to attempt to work on the PR :)
@jackfranklin the purpose of enzyme is mostly to assert things about rendered react components and their behavior.... by manipulating input values you are manipulating the behavior of the component in an unpredictable way...
Some things you can do:
.setProps() or .setState() methods of enzyme..simulate('keydown', { which: 'a' }) or similar@lelandrichardson understood, thanks. I quite like the idea of simulating key presses as a replacement. Would you consider a method to help input lots of keypresses? Eg .simulateKeyPresses('hello') (or whatever) that would call simulate with keydown a bunch of times.
I think that's an interesting idea! The fact that simulating typing is so difficult has been something that's bothered me...
OK - I might have a play and ping a PR if I get anywhere? :)
@lelandrichardson I had a go at this and ended up with something like this:
simulateKeyPresses(characters, ...args) {
for(let i = 0; i < characters.length; i++) {
this.simulate('keyPress', extend({
which: characters.charCodeAt(i),
key: characters[i],
keyCode: characters.charCodeAt(i)
}, args));
}
}
This works in terms of calling an onKeyPress handler if you call it correctly, however the value doesn't actually change, and I'm not so sure as to why.
Additionally, the React test util docs do suggest you should set value and then simulate a change event:
// <input ref="input" />
var node = this.refs.input;
node.value = 'giraffe'
ReactTestUtils.Simulate.change(node);
So I would come back again to the point that it would be nice to have a syntactic method for setting the input value and triggering a change - although I can see why you want to avoid that. I'd love to come up with a simulateKeyPresses similar method that works correctly - any ideas you have would be really cool.
Thanks for a great library :)
Thanks so much for these examples. It'd be great if you could add .value(...) -- @jackfranklin have you been working on this?
I feel like a better approach here would be making a generic and entirely separate library that provided an API, that generated a stream of simulated browser events. Then, enzyme could simply use that library, and could delegate any API decisions to that other module. That other module would then be useful for a number of other projects.
I don't know what that API would look like, but I think solving it in a generic way, for all events and element types, is a hard enough problem that it shouldn't be attempted directly inside a larger project.
@ljharb i agree.
we felt the same pain and ended up building a capybara-like library that takes care of interactions
all: any thoughts on the scope/api/wishlist of such project will be highly appreciated, as i'd love to open source it
@vesln have you made any progress on a capybara like library?
@SpencerCDixon i've built something that we use internally, but truth to be told, it's far away from ready.
i was really hoping for some input from the community on potential API and feature set before i open source it
if you have any suggestions/ideas please let me know
gotcha. Yeah I havn't really spent much time thinking about it but I think it would be cool to mimic capybara's API as much as possible so the library would be very intuitive for people to learn who are coming from a Rails/Ruby background.
I'd definitely be down to put some time into it/help you work on it if you were interested in open sourcing what you did for your company.
@SpencerCDixon sounds awesome, let's join forces! i will ping u when i have something up, even if it's only the initial boilerplate. then we can discuss the api/features publicly and make it happen
:+1: sounds great!
In regards to the original topic (not specific to keypress events) this _does_ seem unintuitive. I just started using enzyme today so maybe there is a easier way of writing this out...
Currently if I wanted to test some event on a input, let's say change event, it looks like this:
input.get(0).value = 'text' // have to get the node...
input.first().simulate('change') // have to use the wrapper...
Versus the TestUtils way:
node.value = str
TestUtils.Simulate.change(node)
The unintuitive part is using 2 different types to do 1 action. Where as with the React TestUtils you're only having to get and deal with the node.
Again new to this, so is there a more streamlined way of doing this common testing task? Right now I'm doing it the TestUtils way.
Not sure if this helps, but personally I've been using:
wrapper.find('input').simulate('change', {target: {value: 'My new value'}});
to test onChange methods and this has worked quite well for me.
@levibuzolic your method worked great for me.
@hnry Are you still using the TestUtils way?
I feel like this could be related to the bug mentioned in this blog post:
http://ianmcnally.me/blog/2015/7/22/react-component-change-testing-works
So that if that was fixed enzyme would be as intuitive.
It's not working for textarea element
It also doesn't work for components using currentTarget instead of target, (you cannot pass custom data to currentTarget the way you do it for target, currentTarget will ignore it)
@levibuzolic Is there a way if i could only set value , i dont have any Onchange or on Blur methods , its just
i just need to set value to the input
@Kamaraju333
i just need to set value to the input
How about below?
const wrapper = mount(<input />);
wrapper.find('input').node.value = 'Test';
Anyone agrees that @levibuzolic solution should be added to documentation? One has to dig trough all the comments here, to find a how to properly simulate input change .. Something this trivial should feel like more fun to accomplish.
textarea works for me, @eduardonunesp.
I didn't think @levibuzolic's solution was working, but I had to swap mockStore with createStore to work for my situation.
import { reducer as formReducer } from 'redux-form'
...
let commentCreate
let form
beforeEach(() => {
commentCreate = sinon.spy()
form = mount(
- <Provider store={mockStore({ form: formReducer })}>
+ <Provider store={createStore(combineReducers({ form: formReducer }))}>
<Container commentCreate={commentCreate} />
</Provider>
).find('form')
})
it('valid and calls commentCreate', () => {
const textarea = form.find('textarea').first()
textarea.simulate('change', { target: { value: 'foo' } })
form.simulate('submit')
expect(commentCreate.callCount).to.equal(1)
})
it('invalid so does not call commentCreate', () => {
form.simulate('submit')
expect(commentCreate.callCount).to.equal(0)
})
I used Redux-Form Test to troubleshoot.
not working for me..
const input = wrapper.find('#my-input');
input.simulate('change',
{ target: { value: 'abc' } }
);
const val = input.node.value;
//val is ''
http://stackoverflow.com/questions/41732318/test-setting-text-value-with-react-and-enzyme
@george-norris-salesforce this wont actually set the value of the text filed, instead it will just trigger your onChange method. If you actually need your DOM to be updated and your onChange doesn't handle this for you then you'll need to actually set the value programatically via wrapper.find('#my-input').node.value = 'abc';
This should be added to docs!
@levibuzolic Didn't seem to work for me. I'm getting Can't add property value, object is not extensible.
edit: All good, I was shallow rendering that particular test, mounting fixed it 馃憤
wrapper.find('...').simulate('change', {target: {value: 'My new value'}});
triggers onChange, but not onChangeText for some reason (I'm using React Native 0.42.3).
Since my component was already an DOM input node, I simulated using the onInput event:
component.simulate('input', { target: { value: 'Type Sample' } });
@jackfranklin don't even reference things by HTML tags. This is actually very bad to do from your tests even though 80% people do it, the 20% of devs who have tested for years know better, and we know how to keep tests less brittle. That is... by searching on 'input' or other tags, you're coupling your tests to the implementation (the DOM). Don't do that...ever. Seriously.
Just do this:
passwordInput = loginForm.find('.ft-password')
passwordInput.value='111'
('ft' stands for 'feature') but that's just a convention I use, you can use whatever you want.
and just add a cssClass to wherever your field is:
<FormControl
bsSize="small"
className="ft-password"
componentClass="input"
onChange={this.props.handlePasswordInput}
placeholder="Enter password"
style={{ width: 300}}
type="password"
/>
(there I'm using a React bootstrap component, you might be using whatever...point in case is I don't couple my tests to 'input' or 'FormControl' or anything like that. My tests are completely ignorant of the implementation always by using feature css markers and searching on those. Plus you can reuse those markers in Casper and other tests in the future).
That will save you from headaches in many ways if you just use css markers like this instead of trying to reference and couple your tests to DOM element names. This is common to do with good testers, we don't find() on HTML elements, we always use feature markers. This is a very common practice done by seasoned TDD'ists.
Places like CarGurus.com do this all the time, just look at their source, you'll see markers everywhere and that's why they're there.
Does not work at all .. Neither input nor textarea. . Neither "simulate" nor "node.value="..
@abdennour I'm using the onBlur event in my forms, so here's how I'm setting input values with node.value and then making sure my methods get called.
const component = mount(<MyComponent {...props />);
const input = component.find("input[type='text']").first();
input.node.value = "something";
input.simulate("blur");
component.update();
expect(props.myUpdateMethod.mock.calls[0]).toEqual([{
// my update method arguments
}]);
I wouldn't recommend using simulate at all - i'd suggest instead getting the prop you want and invoking it directly.
Hereby I confirm that neither 'node' nor 'instance()' is working. Can anybody please help?
My test code:
const wrapper = mount(BeginEndDayPicker(propsBegin));
const input = wrapper.find('input');
expect(input).toHaveLength(2);
wrapper.html() =
<div><div class="row form-group"><div class="col col-md-4"><label for="begin" class=" form-control-label">Begin</label></div><div class="col-12 col-md-8"><div class="DayPickerInput"><input placeholder="Example: 2017-01-01" required="" value="2017-01-01"></div></div></div><div class="row form-group"><div class="col col-md-4"><label for="end" class=" form-control-label">End</label></div><div class="col-12 col-md-8"><div class="DayPickerInput"><input placeholder="Example: 2017-12-31" required="" value="2017-12-31"></div></div></div></div>
I tried with
input.get(0).value = '123';
but got error:
TypeError: Can't add property value, object is not extensible
Then I tried
input.get(0).instance().value = '123';
but got error:
TypeError: input.get(...).instance is not a function
Then I tried:
input.get(0).node.value = '123';
but got error:
TypeError: Cannot set property 'value' of undefined
Finally I tried:
input.first().getDOMNode().nodeValue = '123';
but the expected change function is not called:
expect(handleBeginChange).toBeCalledWith('123');
expect(jest.fn()).toBeCalledWith(expected)
Expected mock function to have been called with:
["123"]
But it was not called.
.get returns a raw node; Try .at(0).instance()?
Either way though, changing the value will never emit a "change" event; there's no way to do that automatically. You don't need to test that React sets up handleBeginChange properly; all you need to do is assert that that function ends up as the onChange prop on the proper element, and you can just trust that React wires it up properly.
I had same issue as above - onChange triggers but doesn't fire the onChangeText had to mock out the component using jest.
jest.mock('TextInput', () => {
const RealComponent = require.requireActual('TextInput');
const React = require('react');
class TextInput extends React.Component {
render() {
return React.createElement('TextInput', {
...this.props,
onChange: this.props.onChangeText,
}, this.props.children);
}
}
TextInput.propTypes = RealComponent.propTypes;
return TextInput;
});
Bit hacky but seems to work for now.
Hello, everyone.
In the end, any working solution?
Simulating various interactions with the components, including onChange, seems quite essential.
I just wanted to come here to say that for anyone trying to do events other than onChange events, the target value trick still works. I was able to combine two tricks on this thread to get what I need.
enzymeWrapper.find('#myId').first()
.simulate('keydown', {keyCode: 13, target: {value:'3537727,3537725,3537723'}});
For anyone trying to update the caret position along with the value of an input:
const input = wrapper.find('input')
const position = value.length
input.simulate('change', { target: { value } })
input.getDOMNode().setSelectionRange(position, position)
input.simulate('select')
For those having problems with onChangeText, the changeText event worked for me.
rowInput.simulate('changeText', 'abc')
@teh-username I get: TypeError: ReactWrapper::simulate() event 'changeText' does not exist
@jsonmaur your solution worked best 馃帀
I tried almost all suggested ways but nothing seems to work for 'textarea'.
<textarea type="text" placeholder="A meaningful text." maxlength="400" role="textbox"></textarea>
Did anyone have any success with simulating (setting) value for textarea?
I found that I had to re-find the input after my change simulation.
const input = mounted.find('input');
expect(input.prop('value')).toBe('Initial'); // PASS
input.simulate('change', { target: { value: 'Changed' } });
// whuuuut?
expect(input.prop('value')).toBe('Changed'); // FAIL...
expect(input.prop('value')).toBe('Initial'); // PASS, seriously?!
// but...:
expect(mounted.find('input').prop('value')).toBe('Changed'); // PASS!
@nemoDreamer I ran into this as well, thank you for the fix! I think it's happening because const input is a stale reference, likely because the onChange caused a re-render in your component. At least, that was what was happening in my component.
Indeed; in v3, all sub-wrappers must be re-found from the root to see updates.
I managed to simulate setting a value for textarea.
wrapper.find("textarea#message").element.value = "a value"
wrapper.find("textarea#message").trigger("input")
The solution I found for having a input with ref to get the value instead of onChange, for example:
<input ref={(value)=>{this._email = value}} >
is to use .instance().value to add a value to the input, submitting the form if you want, and testing the value, like so:
wrapped.find('input[type="email"]').instance().value = "test";
expect(wrapped.find('input[type="email"]').instance().value).toEqual("test");
This is working for me to test filling out the password field on a login form
const passwordInput = component.find('input').at(1);
passwordInput.instance().value = 'y';
passwordInput.simulate('change');
component.find('form').first().simulate('submit');
this works for me:
wrapper.find('#input-value').simulate('change', { target: { '10' } })
then, to check if the value was changed:
expect(wrapper.find('#input-value').props().value).toEqual('10')
It seems like a number of people have working solutions.
@jackfranklin happy to reopen if this is still an issue for you on latest enzyme.
@jsonmaur, how did you clear the mock on the afterEach / afterAll block?
@yuritoledo I get Unexpected token on the closing } immediately after '10'
@timbroder, your version works however I'm unable to expect against the value prior to the change. How did you accomplish that?
@CWSites Sorry, my bad. The correct is:
wrapper.find('#input-value').simulate('change', { target: '10'})
here is my code..
const input = MobileNumberComponent.find('input')
input.props().onChange({target: {
id: 'mobile-no',
value: '1234567900'
}});
MobileNumberComponent.update()
const Footer = (loginComponent.find('Footer'))
expect(Footer.find('Buttons').props().disabled).equals(false)
I have update my DOM with componentname.update()
And then checking submit button validation(disable/enable) with length 10 digit.
@anupammaurya can you create a codesandbox ?
@CWSites I haven't tried expecting before changing it. The value at that point is a known assumption from the test setup
@timbroder what I'm running into is that it only updates the value of instance() and that value is not available to my function that is being called onChange. I still haven't been able to accomplish adjusting the value of an input. Currently I'm serving the value from state so I have to manually alter the state and use that to control the value of my input.
it('validateEmail', () => {
const wrapper = mount(<Login history={[]} />);
const emailUpdated = jest.spyOn(wrapper.instance(), 'emailUpdated');
const validateEmail = jest.spyOn(wrapper.instance(), 'validateEmail');
const loginButton = wrapper.find('Button');
const emailInput = wrapper.find('Input');
wrapper.setState({ email: 'invalid email' });
// simulate login with invalid email
loginButton.simulate('click');
expect(validateEmail).toHaveBeenCalledTimes(1);
expect(wrapper.state().invalid).toEqual(true);
// since I can't update the value directly, update state directly
wrapper.setState({ email: '[email protected]' });
// simulate login with valid email
emailInput.simulate('change', emailInput);
expect(emailUpdated).toHaveBeenCalledTimes(1);
expect(wrapper.state().invalid).toEqual(false);
expect(wrapper.state().email).toEqual('[email protected]');
});
These are the different ways that I've tried to update the input, but none of them pass the value to the function which is called onChange.
// value is not passed to `emailUpdated`
emailInput.instance().value = '[email protected]';
expect(emailInput.instance().value).toEqual('[email protected]'); // returns '[email protected]'
expect(emailInput.props().value).toEqual('[email protected]'); // returns ''
expect(emailInput.value).toEqual('[email protected]'); // returns undefined
// value is not passed to `emailUpdated`
emailInput.props().value = '[email protected]';
expect(emailInput.props().value).toEqual('[email protected]'); // returns '[email protected]'
expect(emailInput.value).toEqual('[email protected]'); // returns undefined
// value is not passed to `emailUpdated`
emailInput.value = '[email protected]';
expect(emailInput.value).toEqual('[email protected]'); // returns '[email protected]'
This is working for me to test filling out the password field on a login form
const passwordInput = component.find('input').at(1); passwordInput.instance().value = 'y'; passwordInput.simulate('change'); component.find('form').first().simulate('submit');
I currently have tested this approach and works flawlessly
This is working for me to test filling out the password field on a login form
const passwordInput = component.find('input').at(1); passwordInput.instance().value = 'y'; passwordInput.simulate('change'); component.find('form').first().simulate('submit');I currently have tested this approach and works flawlessly
I tried this out and typescript would complain about value not existing in instance()
This worked for me:
const attribute = document.createAttribute("value");
attribute.value = "[email protected]";
const emailInput = wrapper.find('input').at(0);
emailInput.getDOMNode().setAttributeNode(attribute);
emailInput.simulate('change');
wrapper.update();
This is what worked for me for simulating text entry into a textarea and testing for a secondary component that was loaded after text entry.
RTFM simulate. Also note that simulate is on the wrapper (the result of find), not on the dom element (which would come from find('myThing').getDOMNode).
it('mounts and comment text entry triggers suggestion menu open with proper results', () => {
const comment = mount(<Comment />);
// Create a fake event with the values needed by handleOnChange
const inputEvent = {persist: () => {}, target: { value: '@pdixon1'}};
comment.find('textarea#commentTextArea').simulate('change', inputEvent);
// Make sure to wait before testing for a dom result.
setTimeout(function(){
const suggestions = comment.find('#nameSuggestionsMenuList');
expect(suggestions.length).toBeGreaterThan(0);
expect(suggestions[0].id).toEqual('pdixon1');
}, 1000);
});
For anyone who failed to get the value right with wrapper.find('input').simulate('change', {target: {value: 'My new value'}});
Here is what I did and it is working
const SomeInputComponent = ({onChangeCallback}) => {
const inputRef = useRef(null);
const handleOnChange = useCallback(()=>{
onChangeCallback(inputRef.current.value)
}, [])
return <div><input ref={inputRef} onChange={handleOnChange} /></div>
}
it('Should change the value ', () => {
let value = '';
const wrapper = mount(
<SomeInputComponent
onChangeCallback={res => {
value = res;
}}
/>,
);
const $input = wrapper.find('input');
$input.at(0).instance().value = 'Changed';
$input.simulate('change');
expect(value).toEqual('Changed');
});
This is working for me to test filling out the password field on a login form
const passwordInput = component.find('input').at(1); passwordInput.instance().value = 'y'; passwordInput.simulate('change'); component.find('form').first().simulate('submit');
This is the only thing that seems to work at all with a password input type -- thank you!
This is working for me to test filling out the password field on a login form
const passwordInput = component.find('input').at(1); passwordInput.instance().value = 'y'; passwordInput.simulate('change'); component.find('form').first().simulate('submit');
This is the only thing that worked for me on a controlled input. Thanks!
Not sure if this helps, but personally I've been using:
wrapper.find('input').simulate('change', {target: {, value: 'My new value'}});to test
onChangemethods and this has worked quite well for me.
I've been using this too, but I want to ask that what if I'm passing data in the onChange like this
onChange={e => this.myOnChange(e, data)}
Now, I'm confused about how can I pass data in the onChange on the test?
@Puneet1796 that depends on where data comes from. You can't alter that from just having access to the onChange prop.
@ljharb How does it matter?, According to the documentation simulate on mounted components can't let data pass through it, now here what enzyme fails to test the onChange.
@Puneet1796 i'm not sure what you mean - it matters because the way the JS language works prevents any interception of data there.
@ljharb I have an array of values and I'm iterating over each value and renders a component everytime, say list item, but I want to pass index in the change so that I'll know which list item is updated. But in the case of testing, I found out that there's no way mentioned in the documentation to pass the value in onChange via simulate.
The value that I want to pass is the index of that particular list item.
Based on the code you passed, it's impossible via any means.
What i had to do to update the value of a form field that was only referenced with a ref.
const inputField = wrapper.find('input[type="text"]');
inputField.getDOMNode().value = "my updated value";
inputField.simulate('whatever you are listening for');
This is working for me to test filling out the password field on a login form
const passwordInput = component.find('input').at(1); passwordInput.instance().value = 'y'; passwordInput.simulate('change'); component.find('form').first().simulate('submit');
this one actually worked for me;
All other solutions in this thread gave different errors of the no function x of undefined sort (including and especially the doc's way with simulate('change', { target: /* ... */ }))
Most helpful comment
Not sure if this helps, but personally I've been using:
to test
onChangemethods and this has worked quite well for me.