I have been learning React, Babel, Semantic UI, and Jest over the last couple of weeks. I haven't really run into too many issues with my components not rendering in the browser, but I have run into issues with rendering when writing unit tests with Jest.
The SUT is as follows:
EditUser.jsx
var React = require('react'); var { browserHistory, Link } = require('react-router'); var $ = require('jquery'); import Navigation from '../Common/Navigation'; const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/; var EditUser = React.createClass({ getInitialState: function() { return { email: '', firstName: '', lastName: '', phone: '', role: '' }; }, handleSubmit: function(e) { e.preventDefault(); var data = { "email": this.state.email, "firstName": this.state.firstName, "lastName": this.state.lastName, "phone": this.state.phone, "role": this.state.role }; if($('.ui.form').form('is valid')) { $.ajax({ url: apiUrl + '/api/users/' + this.props.params.userId, dataType: 'json', contentType: 'application/json', type: 'PUT', data: JSON.stringify(data), success: function(data) { this.setState({data: data}); browserHistory.push('/Users'); $('.toast').addClass('happy'); $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.'); $('.toast').transition('fade up', '500ms'); setTimeout(function(){ $('.toast').transition('fade up', '500ms').onComplete(function() { $('.toast').removeClass('happy'); }); }, 3000); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); $('.toast').addClass('sad'); $('.toast').html("Something bad happened: " + err.toString()); $('.toast').transition('fade up', '500ms'); setTimeout(function(){ $('.toast').transition('fade up', '500ms').onComplete(function() { $('.toast').removeClass('sad'); }); }, 3000); }.bind(this) }); } }, handleChange: function(e) { var nextState = {}; nextState[e.target.name] = e.target.value; this.setState(nextState); }, componentDidMount: function() { $('.dropdown').dropdown(); $('.ui.form').form({ fields: { firstName: { identifier: 'firstName', rules: [ { type: 'empty', prompt: 'Please enter a first name.' }, { type: 'doesntContain[<script>]', prompt: 'Please enter a valid first name.' } ] }, lastName: { identifier: 'lastName', rules: [ { type: 'empty', prompt: 'Please enter a last name.' }, { type: 'doesntContain[<script>]', prompt: 'Please enter a valid last name.' } ] }, email: { identifier: 'email', rules: [ { type: 'email', prompt: 'Please enter a valid email address.' }, { type: 'empty', prompt: 'Please enter an email address.' }, { type: 'doesntContain[<script>]', prompt: 'Please enter a valid email address.' } ] }, role: { identifier: 'role', rules: [ { type: 'empty', prompt: 'Please select a role.' } ] }, phone: { identifier: 'phone', optional: true, rules: [ { type: 'minLength[10]', prompt: 'Please enter a valid phone number of at least {ruleValue} digits.' }, { type: 'regExp', value: phoneRegex, prompt: 'Please enter a valid phone number.' } ] } } }); $.ajax({ url: apiUrl + '/api/users/' + this.props.params.userId, dataType:'json', cache: false, success: function(data) { this.setState({data: data}); this.setState({email: data.email}); this.setState({firstName: data.firstName}); this.setState({lastName: data.lastName}); this.setState({phone: data.phone}); this.setState({role: data.role}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, render: function () { return ( <div className="container"> <Navigation active="Users"/> <div className="ui segment"> <h2>Edit User</h2> <div className="required warning"> <span className="red text">*</span><span> Required</span> </div> <form className="ui form" onSubmit={this.handleSubmit} data={this.state}> <h4 className="ui dividing header">User Information</h4> <div className="ui three column grid field"> <div className="row fields"> <div className="column field required"> <label>First Name</label> <input type="text" name="firstName" value={this.state.firstName} onChange={this.handleChange}/> </div> <div className="column field required"> <label>Last Name</label> <input type="text" name="lastName" value={this.state.lastName} onChange={this.handleChange}/> </div> <div className="column field required"> <label>Email</label> <input type="text" name="email" value={this.state.email} onChange={this.handleChange}/> </div> </div> </div> <div className="ui three column grid field"> <div className="row fields"> <div className="column field required"> <label>User Role</label> <select className="ui dropdown" name="role" onChange={this.handleChange} value={this.state.role}> <option value="SuperAdmin">Super Admin</option> </select> </div> <div className="column field"> <label>Phone</label> <input name="phone" value={this.state.phone} onChange={this.handleChange}/> </div> </div> </div> <div className="ui three column grid"> <div className="row"> <div className="right floated column"> <div className="right floated large ui buttons"> <Link to="/Users" className="ui button">Cancel</Link> <button className="ui button primary" type="submit">Save</button> </div> </div> </div> </div> <div className="ui error message"></div> </form> </div> </div> ); } }); module.exports = EditUser;
The associated test file is as follows:
EditUser.test.js
var React = require('react'); var Renderer = require('react-test-renderer'); var jQuery = require('jquery'); require('../../../semantic/dist/components/dropdown'); import EditUser from '../../../app/components/Users/EditUser'; it('renders correctly', () => { const component = Renderer.create( <EditUser /> ).toJSON(); expect(component).toMatchSnapshot(); });
The issue that I am seeing when I run jest
:
FAIL test/components/Users/EditUser.test.js ● Test suite failed to run ReferenceError: jQuery is not defined at Object.<anonymous> (semantic/dist/components/dropdown.min.js:11:21523) at Object.<anonymous> (test/components/Users/EditUser.test.js:6:370) at process._tickCallback (node.js:369:9)
You are doing it in right way but one simple mistake.
You have to tell jest not to mock jquery
To be clear,
from https://www.phpied.com/jest-jquery-testing-vanilla-app/ under 4th subtitle Testing Vanilla
[It talks about testing a Vanilla app, but it perfectly describe about Jest]
The thing about Jest is that it mocks everything. Which is priceless for unit testing. But it also means you need to declare when you don't want something mocked.
That is
jest.unmock(moduleName)
From Facebook's documentation
unmock
Indicates that the module system should never return a mocked version of the specified module from require() (e.g. that it should always return the real module).The most common use of this API is for specifying the module a given test intends to be testing (and thus doesn't want automatically mocked).
It returns the jest object for chaining.
Note : Previously it was
dontMock
.When using babel-jest, calls to unmock will automatically be hoisted to the top of the code block. Use dontMock if you want to explicitly avoid this behavior.
You can see the full documentation here Facebook's Documentation Page in Github .
Also use const
instead of var
in require. That is
const $ = require('jquery');
So the code looks like
jest.unmock('jquery'); // unmock it. In previous versions, use dontMock instead var React = require('react'); var { browserHistory, Link } = require('react-router'); const $ = require('jquery'); import Navigation from '../Common/Navigation'; const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/; var EditUser = React.createClass({ getInitialState: function() { return { email: '', firstName: '', lastName: '', phone: '', role: '' }; }, handleSubmit: function(e) { e.preventDefault(); var data = { "email": this.state.email, "firstName": this.state.firstName, "lastName": this.state.lastName, "phone": this.state.phone, "role": this.state.role }; if($('.ui.form').form('is valid')) { $.ajax({ url: apiUrl + '/api/users/' + this.props.params.userId, dataType: 'json', contentType: 'application/json', type: 'PUT', data: JSON.stringify(data), success: function(data) { this.setState({data: data}); browserHistory.push('/Users'); $('.toast').addClass('happy'); $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.'); $('.toast').transition('fade up', '500ms'); setTimeout(function(){ $('.toast').transition('fade up', '500ms').onComplete(function() { $('.toast').removeClass('happy'); }); }, 3000); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); $('.toast').addClass('sad'); $('.toast').html("Something bad happened: " + err.toString()); $('.toast').transition('fade up', '500ms'); setTimeout(function(){ $('.toast').transition('fade up', '500ms').onComplete(function() { $('.toast').removeClass('sad'); }); }, 3000); }.bind(this) }); } }, handleChange: function(e) { var nextState = {}; nextState[e.target.name] = e.target.value; this.setState(nextState); }, componentDidMount: function() { $('.dropdown').dropdown(); $('.ui.form').form({ fields: { firstName: { identifier: 'firstName', rules: [ { type: 'empty', prompt: 'Please enter a first name.' }, { type: 'doesntContain[<script>]', prompt: 'Please enter a valid first name.' } ] }, lastName: { identifier: 'lastName', rules: [ { type: 'empty', prompt: 'Please enter a last name.' }, { type: 'doesntContain[<script>]', prompt: 'Please enter a valid last name.' } ] }, email: { identifier: 'email', rules: [ { type: 'email', prompt: 'Please enter a valid email address.' }, { type: 'empty', prompt: 'Please enter an email address.' }, { type: 'doesntContain[<script>]', prompt: 'Please enter a valid email address.' } ] }, role: { identifier: 'role', rules: [ { type: 'empty', prompt: 'Please select a role.' } ] }, phone: { identifier: 'phone', optional: true, rules: [ { type: 'minLength[10]', prompt: 'Please enter a valid phone number of at least {ruleValue} digits.' }, { type: 'regExp', value: phoneRegex, prompt: 'Please enter a valid phone number.' } ] } } }); $.ajax({ url: apiUrl + '/api/users/' + this.props.params.userId, dataType:'json', cache: false, success: function(data) { this.setState({data: data}); this.setState({email: data.email}); this.setState({firstName: data.firstName}); this.setState({lastName: data.lastName}); this.setState({phone: data.phone}); this.setState({role: data.role}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, render: function () { return ( <div className="container"> <Navigation active="Users"/> <div className="ui segment"> <h2>Edit User</h2> <div className="required warning"> <span className="red text">*</span><span> Required</span> </div> <form className="ui form" onSubmit={this.handleSubmit} data={this.state}> <h4 className="ui dividing header">User Information</h4> <div className="ui three column grid field"> <div className="row fields"> <div className="column field required"> <label>First Name</label> <input type="text" name="firstName" value={this.state.firstName} onChange={this.handleChange}/> </div> <div className="column field required"> <label>Last Name</label> <input type="text" name="lastName" value={this.state.lastName} onChange={this.handleChange}/> </div> <div className="column field required"> <label>Email</label> <input type="text" name="email" value={this.state.email} onChange={this.handleChange}/> </div> </div> </div> <div className="ui three column grid field"> <div className="row fields"> <div className="column field required"> <label>User Role</label> <select className="ui dropdown" name="role" onChange={this.handleChange} value={this.state.role}> <option value="SuperAdmin">Super Admin</option> </select> </div> <div className="column field"> <label>Phone</label> <input name="phone" value={this.state.phone} onChange={this.handleChange}/> </div> </div> </div> <div className="ui three column grid"> <div className="row"> <div className="right floated column"> <div className="right floated large ui buttons"> <Link to="/Users" className="ui button">Cancel</Link> <button className="ui button primary" type="submit">Save</button> </div> </div> </div> </div> <div className="ui error message"></div> </form> </div> </div> ); } }); module.exports = EditUser;
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