Как правильно издеваться над сторонними библиотеками (такими как jQuery и Semantic UI) с помощью Jest?

Последние пару недель я изучал React, Babel, Semantic UI и Jest. На самом деле я не сталкивался со слишком большим количеством проблем с моими компонентами, которые не отображались в браузере, но я сталкивался с проблемами с отображением при написании модульных тестов с помощью Jest.

СУТ выглядит следующим образом:

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)

person Reece Long    schedule 30.09.2016    source источник
comment
Мне понадобится знающая помощь, чтобы присудить эту награду, так как я ничего не знаю о предмете. Кто угодно?   -  person Pekka    schedule 06.03.2017
comment
Вот аналогичный вопрос, который может помочь: jquery без модульных тестов jest"> stackoverflow.com/questions/27189254/   -  person Martina    schedule 25.03.2017


Ответы (2)


Вы делаете это правильно, но есть одна простая ошибка.

Вы должны сказать шутке, чтобы не издеваться над jquery

Чтобы было ясно,

от https://www.phpied.com/jest-jquery-testing-vanilla-app/ под 4-м подзаголовком Testing Vanilla

[Здесь говорится о тестировании ванильного приложения, но прекрасно описывается Jest]

Суть Jest в том, что она высмеивает все. Что бесценно для модульного тестирования. Но это также означает, что вам нужно объявить, когда вы не хотите, чтобы над чем-то издевались.

То есть

jest.unmock(moduleName)

Из документации Facebook
unmock Указывает, что система модулей никогда не должна возвращать фиктивную версию указанного модуля из require() (например, она всегда должна возвращать настоящий модуль).

Чаще всего этот API используется для указания модуля, который данный тест намеревается тестировать (и, следовательно, он не хочет автоматически имитироваться).

Он возвращает объект шутки для цепочки.

Примечание. Ранее это было dontMock.

При использовании babel-jest вызовы unmock будут автоматически подниматься в начало блока кода. Используйте dontMock, если вы хотите явным образом избежать такого поведения.
Полную документацию можно посмотреть здесь Страница документации Facebook в Github.

Также используйте const вместо var в require. То есть

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;
person Sagar V    schedule 04.04.2017

В вашей конфигурации шутки поставьте ..

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

В jestsetup.js вам нужно добавить $ и jQuery как глобальные..

import $ from 'jquery';
global.$ = $;
global.jQuery = $;
person ridermansb    schedule 02.06.2017