Hello, I am new to Jest. I am testing a login page and I am simulating text change in my form. I am setting a value to the input and then I am triggering the change event. Everything works fine, the expected function is being called, the input box holds the value but the state of the component is not being updated. Here is part of related code:
Container:
export class LoginContainer extends Component {
constructor() {
super()
this.state = {
userName : '',
password : ''
}
}
/**
* Set value of state in order to show in the input box
*
* @param {String} value value to be set in the field
* @param {String} propName field name
*/
handleInputChange = (value, propName) => {
this.setState({
...this.state,
[propName] : value
})
}
/**
* Initialte login call on form submit
*
* @param {Object} event Event object
*/
handleLogin = (event) => {
event.preventDefault()
const {userName, password} = this.state
this.props.userLogin({
userName,
password
})
.then(() => {
this.props.history.push('/')
})
}
// ... some other class functions..
render() {
const { userName, password } = this.state
const { isFetching, error } = this.props
return (
<Login
error={error}
userName={userName}
password={password}
isFetching={isFetching}
onFormSubmit={this.handleLogin}
onInputChange={this.handleInputChange}
/>
)
}
}
... connecting the component with react-redux connect...
Component:
<form className={`center ${styles['login-form']}`}>
<div className={styles['input-container']}>
<input
type='text'
value={userName}
placeholder='Enter Email'
onChange={(event) => onInputChange(event.target.value, 'userName')}
/>
</div>
<div className={styles['input-container']}>
<input
type='password'
value={password}
placeholder='Enter Password'
onChange={(event) => onInputChange(event.target.value, 'password')}
/>
</div>
<div>
<button
type='submit'
onClick={onFormSubmit}
disabled={isFetching}
className={`button ${styles.submit} ${isFetching && styles.disabled}`}
>{isFetching ? 'Loading...' : 'Login'}</button>
</div>
</form>
Test:
describe('>>> Login Container -- Shallow Rendering', () => {
const initialState = userActionCreators.initialState
let mountedScreen
const loginScreen = (customState = {}) => {
if (!mountedScreen) {
const updatedState = {
...initialState,
...customState
}
mountedScreen = mount(<LoginContainer {...updatedState} />)
}
return mountedScreen
}
beforeEach(() => {
mountedScreen = undefined
})
it('Simulates text change in input box', () => {
const tree = loginScreen()
const instance = tree.instance()
const handleInputChangeSpy = jest.spyOn(instance, 'handleInputChange')
const input = tree
.find('input')
.first()
instance.forceUpdate()
input.value = 'test'
input.simulate('change', input)
console.log('&&&&&&&&&&&&&&&&&&&&&')
console.log(instance.state) // userName and password are empty here
console.log('&&&&&&&&&&&&&&&&&&&&&')
expect(handleInputChangeSpy).toHaveBeenCalledWith('test', 'userName')
})
})
Console Error:
● >>> Login Container -- Shallow Rendering › Simulates text change in input box
expect(jest.fn()).toHaveBeenCalledWith(expected)
Expected mock function to have been called with:
"test" as argument 1, but it was called with "".
Let me know if you need anything else.
What versions of react, enzyme, etc?
Here is the link to package.json
@iiison your issue is that, because you're using class properties, the reference to handleInputChange is already stored in the enzyme wrapper before you spy on it.
Instead, change handleInputChange to be an instance method, and add this.handleInputChange = this.handleInputChange.bind(this) into your constructor - then you can spy on LoginContainer.prototype.handleInputChange before you create your enzyme wrapper.
@ljharb I have a similar problem.
handleChange(event) {
const {name, value} = event.target;
}
(..)
<input
className='form__control'
id='username'
type='text'
value={username}
placeholder='Username'
onChange={this.handleChange}
required
/>
In the test:
const usernameInput = wrapper.find("#username")
usernameInput.props().val = 'correctUsername'
usernameInput.simulate('change');
I see the event in the handleChange but there is no name and value in them. It works in a browser. I'm a bit confused whether this is a bug, lack of feature, or intended behavior?
I can have this work doing
usernameInput.simulate('change', {target: {name: 'username', value: 'correctUsername'}});
But this seems error prone, especially if I wanted to generalize this event with page-object pattern.
Also if I just use the second argument of simulate then the actual value of the input is unchanged ,so if somewhere else I'm using refs to get value, that doesn't work.
You can see how it works in a browser when you type:
https://codesandbox.io/s/2jo8pqpl2r?expanddevtools=1
Thanks for a great work on this, help would be highly appreciated :-)
const usernameInput = wrapper.find("#username")
usernameInput.instance().value = "correctUsername"
usernameInput.simulate('change');
Works! :-) Please disregard the comment above.
@iiison I think this might actually work for you as well
@Igandecki tried that but keep getting ShallowWrapper::instance() can only be called on the root
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.
Most helpful comment
Works! :-) Please disregard the comment above.
@iiison I think this might actually work for you as well