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?
In Jest configuration with JSDOM
global.window === global
, so it can be mocked onwindow
.It's preferable to mock it like
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, thatalert
mock wasn't called is just an evidence that something went wrong.In this case
click
event on a button won't causesubmit
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 ofmount
.It likely should be:
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 expectedonSubmit
prop and callformWrapper.instance()._handleSubmit(...)
directly.Instead of
window
, you can useglobal
.This is because browsers use the
window
name, while nodejs use theglobal
name.