A few of the Enzyme examples show using a class method of a component, but instead of using the instance()
function to get the class instance and test the method's return value, instead they simulate events, then check the side effects of those events called from the method you want to test.
In the below example, unit test coverage will say the Failure
part of the onNameBlur
method was covered.
class AddUserForm extends React.Component {
...
onNameBlur = event => {
const name = event.target.value
validName(name).matchWith({
Failure: ({value}) => this.setState({name, nameErrors: value.join(' '), clean: false}),
Success: () => this.setState({name, nameErrors: undefined, clean: false})
})
}
...
<TextField hintText="Your Full Name" onBlur={this.onNameBlur} />
...
}
it('should work', ()=> {
const addUserForm = shallow(<AddUserForm />)
addUserForm.find('[hintText="Your Full Name"]').simulate('blur', {target: {value: ' '}})
expect(addUserForm.state('nameErrors')).toEqual('Invalid empty name, cannot be a blank string, how about J instead?')
})
In a pure way, you instead simply write functions, and bind to them in your JSX. What is NOT shown here is you still should do the above test to verify once you integrate them, they do in fact work. My friend Steven Sacks has pointed out I could simply use the Enyzme instance()
function to call the methods directly, but you'd still have to be ok with the state and this
keyword. My FP spidey sense hates it, but my pragmattic get things done + still have 100% coverage is ok with it.
export const onNameBlur = curryN(2, (self, event) => {
const name = event.target.value
return validName(name).matchWith({
Failure: ({value}) => self.setState({name, nameErrors: value.join(' '), clean: false}),
Success: () => self.setState({name, nameErrors: undefined, clean: false})
})
})
...
<TextField hintText="Your Full Name" onBlur={onNameBlur(this)} />
...
it('should work', ()=> {
const result = onNameBlur({ setState: identity }, {target: {value: ' '}})
expect(result.nameErrors).toEqual('Invalid empty name, cannot be a blank string, how about J instead?')
})
If you're not down using closures or curried functions and just want a React class and you're fine with using instance()
, then ensure the function at least returns a value.
onNameBlur = event => {
const name = event.target.value
return validName(name).matchWith({
Failure: ({value}) => {
const failedState = {name, nameErrors: value.join(' '), clean: false}
this.setState(failedState)
return failedState
},
Success: () => {
const successState = {name, nameErrors: undefined, clean: false}
this.setState(successState)
return successState
}
})
}
it('should work', ()=> {
const addUserForm = shallow(<AddUserForm />)
const result = addUserForm.instance().onNameBlur({target: {value: ' '}})
expect(result.nameErrors).toEqual('Invalid empty name, cannot be a blank string, how about J instead?')
})
You'll notice in the example that you have to do a lot of verbose coding to compensate for setState
not returning a value. With a simple helper method:
const setState = (self, o) => {
self.setState(o)
return o
}
You can then modify it so when you start doing this a lot, your code stays more concise:
onNameBlur = event => {
const name = event.target.value
return validName(name).matchWith({
Failure: ({value}) => setState(this, {name, nameErrors: value.join(' '), clean: false}),
Success: () => setState(this, {name, nameErrors: undefined, clean: false})
})
}