I have the following React component:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = this._createEmptyTodo();
}
render() {
this.i18n = this.context;
return (
<div className="form">
<form onSubmit={this._handleSubmit.bind(this)}>
<input
placeholder={this.i18n.placeholders.addTitle}
type="text"
value={this.state.title}
onChange={this._handleTitleChange.bind(this)}></input>
<textarea
placeholder={this.i18n.placeholders.addDescription}
value={this.state.description}
onChange={this._handleDescriptionChange.bind(this)}></textarea>
<button>{this.i18n.buttons.submit}</button>
</form>
</div>
);
}
_handleTitleChange(e) {
this.setState({
title: e.target.value
});
}
_handleDescriptionChange(e) {
this.setState({
description: e.target.value
});
}
_handleSubmit(e) {
e.preventDefault();
var todo = {
date: new Date().getTime(),
title: this.state.title.trim(),
description: this.state.description.trim(),
done: false
};
if (!todo.title) {
alert(this.i18n.errors.title);
return;
}
if (!todo.description) {
alert(this.i18n.errors.description);
return;
}
this.props.showSpinner();
this.props.actions.addTodo(todo);
this.setState(this._createEmptyTodo());
}
_createEmptyTodo() {
return {
"pkey": null,
"title": "",
"description": ""
};
}
}
And the related test:
const i18nContext = React.createContext();
Form.contextType = i18nContext;
describe('The <Form> component', () => {
var wrapper;
var showSpinner;
var actions = {}
beforeEach(() => {
showSpinner = jest.fn();
actions.addTodo = jest.fn();
wrapper = mount(<i18nContext.Provider value={i18n["en"]}>
<Form
showModalPanel={showSpinner}
actions={actions} />
</i18nContext.Provider>);
});
test("validate its input", () => {
window.alert = jest.fn();
wrapper.find("button").simulate("click");
expect(window.alert.mock.calls.length).toBe(1);//<<< this FAILS!
});
});
This form, when the button gets clicked, it simply alerts a message using alert
.
Now when I run the test I get this:
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
Which is a failure because the mock does not get called apparently. But I promise you that the form component does alert a message when clicking on its button.
I suspect that, for some reasons, the mocked window.alert
does not get used by the Form
component when the click is performed programmatically using enzyme.
Anyone?
alert = jest. fn(); wrapper. find("button"). simulate("click"); expect(window.
the alert isn't actually 'rendered' under test, so the only way (that I know of) is to inspect the mock and manually trigger the onPress that was passed to it.
In Jest configuration with JSDOM global.window === global
, so it can be mocked on window
.
It's preferable to mock it like
jest.spyOn(window, 'alert').mockImplementation(() => {});
because window.alert = jest.fn()
contaminates other tests in this suite.
The problem with blackbox testing is that troubleshooting is harder, also relying on the behaviour that expected from real DOM may cause problems because Enzyme doesn't necessary support this behaviour. It's unknown whether the actual problem, handleSubmit
was called or not, that alert
mock wasn't called is just an evidence that something went wrong.
In this case click
event on a button won't cause submit
event on parent form because Enzyme doesn't support that by design.
A proper unit-testing strategy is to set up spies or mocks for all units except tested one, which is submit event handler. It usually involves shallow
instead of mount
.
It likely should be:
jest.spyOn(window, 'alert').mockImplementation(() => {});
const formWrapper = wrapper.find(Form).dive();
jest.spyOn(formWrapper.instance(), '_handleSubmit');
formWrapper.find("form").simulate("submit");
expect(formWrapper.instance()._handleSubmit).toBeCalled();
expect(window.alert).toBeCalledWith(...);
State should be changed directly with formWrapper.setState
instead of DOM events simulation.
A more isolated unit test would be to assert that form
was provided with expected onSubmit
prop and call formWrapper.instance()._handleSubmit(...)
directly.
Instead of window
, you can use global
.
global.alert = jest.fn();
This is because browsers use the window
name, while nodejs use the global
name.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With