在过去的几周里,我一直在学习React,Babel,Semantic UI和Jest。我的组件没有在浏览器中呈现,我真的遇到太多问题,但是当我用Jest编写单元测试时,我 遇到渲染问题。
SUT如下:
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;
关联的测试文件如下:
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();
});
我在运行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)
答案 0 :(得分:2)
你是以正确的方式做到这一点,但一个简单的错误。
你必须告诉jest不要模拟jquery
要清楚,
来自https://www.phpied.com/jest-jquery-testing-vanilla-app/ 第4个副标题测试香草
[它谈到测试一个Vanilla应用程序,但它完美地描述了 Jest ]
关于Jest的事情就是嘲笑一切。这对单元测试来说是无价之宝。但这也意味着你需要在不想要嘲笑的时候申报。
那是
jest.unmock(moduleName)
来自Facebook的文档
unmock
表示模块系统永远不会从require()返回指定模块的模拟版本(例如,它应该始终返回实际模块)。此API的最常见用途是指定要测试的给定测试的模块(因此不需要自动模拟)。
返回链接的jest对象。
注意:以前是
dontMock
。当使用babel-jest时,对unmock的调用将自动提升到代码块的顶部。 如果要明确避免此行为,请使用dontMock 您可以在此处查看完整文档Facebook's Documentation Page in Github 。
在require中使用const
代替var
。那是
const $ = require('jquery');
所以代码看起来像
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;
答案 1 :(得分:0)
在你的开玩笑配置中..
"setupFiles": ["./jestsetup.js"]
在jestsetup.js
中,您需要将$
和jQuery
添加为全局..
import $ from 'jquery';
global.$ = $;
global.jQuery = $;