2016-09-30 208 views
21

在过去几周里,我一直在学习React,Babel,Semantic UI和Jest。我没有真正遇到过很多问题,而我的组件没有在浏览器中渲染,但是当用Jest编写单元测试时,我的会遇到渲染问题。如何正确使用Jest模拟第三方库(如jQuery和Semantic UI)?

的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

因为我对这个题目一无所知,所以我需要知识渊博的帮助来奖励这个赏金。任何人? –

+0

这是一个类似的问题,可能会有所帮助:http://stackoverflow.com/questions/27189254/react-component-using-jquery-without-require-jest-unit-tests – Martina

回答

2

你在正确的方式,但一个简单的错误做。

你必须告诉开玩笑不要嘲笑的jQuery

需要明确的是,

https://www.phpied.com/jest-jquery-testing-vanilla-app/下4字幕测试香草

[它讨论关于测试一个Vanilla应用程序,但它完美的描述约Jest]

关于JEST的事情是它嘲笑一切。单元测试是无价的。但这也意味着你需要宣布什么时候你不想要嘲弄的东西。

也就是说

jest.unmock(moduleName) 

从Facebook的文档
unmock指示模块系统应该从不要求()(例如,它应该返回指定模块的嘲笑版本总是返回真实的模块)。

此API的最常见用途是用于指定给定测试打算测试的模块(因此不需要自动模拟)。

它返回链接的笑话对象。

注:以前它是dontMock

使用babel-jest时,解除锁定的呼叫将自动提升到代码块的顶部。如果要明确避免此行为,请使用dontMock
你可以在这里看到完整的文档Facebook's Documentation Page in Github

另请使用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; 
0

在你开玩笑配置放..

"setupFiles": ["./jestsetup.js"] 

jestsetup.js你需要添加$jQuery全球..

import $ from 'jquery'; 
global.$ = $; 
global.jQuery = $; 
相关问题