Question Comment est-ce que je me moque correctement des bibliothèques tierces (comme jQuery et l'interface sémantique) en utilisant Jest?


J'ai appris React, Babel, Semantic UI et Jest au cours des dernières semaines. Je n'ai pas vraiment rencontré trop de problèmes avec mes composants ne pas rendre dans le navigateur, mais je avoir rencontrez des problèmes de rendu lors de l'écriture des tests unitaires avec Jest.

Le SUT est comme suit:

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;

Le fichier de test associé est le suivant:

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();
});

Le problème que je vois quand je cours 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)

21
2017-09-30 22:27


origine


Réponses:


Vous le faites de la bonne manière, mais une simple erreur.

Il faut que tu te moques de ne pas te moquer de jquery

Pour être clair,

de https://www.phpied.com/jest-jquery-testing-vanilla-app/ sous le 4ème sous-titre Test de vanille 

[Il parle de tester une application Vanilla, mais elle décrit parfaitement Plaisanter] 

La chose à propos de Jest est qu’elle se moque de tout. Ce qui est inestimable pour les tests unitaires. Mais cela signifie également que vous devez déclarer quand vous ne voulez pas que quelque chose se moque.

C'est

jest.unmock(moduleName)

De la documentation de Facebook
unmock Indique que le système de module devrait ne jamais renvoyer une version simulée du module spécifié à partir de require () (par exemple, il doit toujours renvoyer le module réel). 

L'utilisation la plus courante de cette API est de spécifier le module qu'un test donné a l'intention de tester (et ne veut donc pas se moquer automatiquement).

Il renvoie l'objet de plaisanterie pour le chaînage.

Note: Auparavant, c'était dontMock.

Lors de l'utilisation de babel-jest, les appels à désamorcer seront automatiquement placés en haut du bloc de code. Utiliser dontMock si vous voulez éviter explicitement ce comportement.
  Vous pouvez voir la documentation complète ici Page de documentation de Facebook dans Github .

Utiliser aussi const au lieu de var en besoin. C'est

const $ = require('jquery');

Donc, le code ressemble à

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;

2
2018-04-04 06:43



Dans votre configuration de plaisanterie, mettez ..

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

Dans jestsetup.js vous devez ajouter $ et jQuery comme global ..

import $ from 'jquery';
global.$ = $;
global.jQuery = $;

0
2018-06-02 03:17