如何测试需要 jquery 的 ES6 类? [英] How to test an ES6 class that needs jquery?

查看:15
本文介绍了如何测试需要 jquery 的 ES6 类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个需要 jquery 的 ES6 模块.

I have an ES6 module that needs jquery.

import $ from 'jquery';

export class Weather {
    /**
     * Constructor for Weather class
     *
     * @param latitude
     * @param longitude
     */
    constructor(latitude, longitude) {
        this.latitude  = latitude;
        this.longitude = longitude;
    }

    /**
     * Fetches the weather using API
     */
    getWeather() {
        return $.ajax({
            url: 'http://localhost:8080/weather?lat=' + this.latitude + '&lon=' + this.longitude,
            method: "GET",
        }).promise();
    }
}

在我的 main 模块中使用时,模块工作正常,但问题出在我为它编写的测试中.

Module works fine when use it in my main module but the issue is with the test that I am writing for it.

测试如下:

import {Weather} from '../js/weather';
import chai from 'chai';
import sinon from 'sinon';

chai.should();

describe('weatherbot', function() {
    beforeEach(function() {
        this.xhr = sinon.useFakeXMLHttpRequest();

        this.requests = [];
        this.xhr.onCreate = function(xhr) {
            this.requests.push(xhr);
        }.bind(this);
    });

    afterEach(function() {
        this.xhr.restore();
    });

    it('should return a resolved promise if call is successful', (done) => {
        let weather = new Weather(43.65967339999999, -79.72506369999999);

        let data = '{"coord":{"lon":-79.73,"lat":43.66},"weather":[{"id":521,"main":"Rain","description":"shower rain","icon":"09d"}],"base":"stations","main":{"temp":15.28,"pressure":1009,"humidity":82,"temp_min":13,"temp_max":17},"visibility":24140,"wind":{"speed":7.2,"deg":30},"clouds":{"all":90},"dt":1496770020,"sys":{"type":1,"id":3722,"message":0.0047,"country":"CA","sunrise":1496741873,"sunset":1496797083},"id":5907364,"name":"Brampton","cod":200}';

        weather.getWeather().then((data) => {
            expect(data.main.temp).to.equal(15.28);
            done();
        });

        this.requests[0].respond("GET", "/weather?lat=43.659673399999996&lon=-79.72506369999999", [
            200, {"Content-Type":"application/json"}, JSON.stringify(data)
        ]);
    });
});

这是我的package.json:

{
  "devDependencies": {
    "babel-core": "^6.24.1",
    "babel-loader": "^6.1.0",
    "babel-polyfill": "^6.3.14",
    "babel-preset-es2015": "^6.1.18",
    "chai": "^3.5.0",
    "copy-webpack-plugin": "^0.2.0",
    "css-loader": "^0.28.0",
    "extract-text-webpack-plugin": "^2.1.0",
    "file-loader": "^0.11.1",
    "mocha": "^3.4.1",
    "mocha-webpack": "^1.0.0-beta.1",
    "qunitjs": "^2.3.2",
    "sinon": "^2.2.0",
    "style-loader": "^0.16.1",
    "svg-inline-loader": "^0.7.1",
    "webpack": "*",
    "webpack-dev-server": "^1.12.1",
    "webpack-node-externals": "^1.6.0"
  },
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch --display-error-details",
    "start": "webpack-dev-server --hot --inline --port 8383",
    "test": "mocha --compilers js:babel-core/register ./test/*.js",
    "test:watch": "npm run test -- --watch"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  },
  "dependencies": {
    "bootstrap": "^3.3.7",
    "jquery": "^3.2.1",
    "webpack": "*"
  }
}

如您所见,我只需要执行 npm test 即可运行测试.

As you can see I only have to do npm test to run the test.

什么时候做npm test,我得到这个错误:

When do npm test, I get this error:

TypeError: _jquery2.default.ajax is not a function
      at Weather.getWeather (js/weather.js:19:18)
      at Context.<anonymous> (test/index.js:26:17)

但是我在模块中导入了jquery,为什么会发生这种情况?

But I am importing the jquery in the module, why it might be happening?

推荐答案

这里有两个主要问题.首先当然是您需要修复导入问题,但这与测试无关.在进行测试之前,您需要解决这个问题,这可能与构建工具的配置有关,而不是在 Node.js 中运行.您应该为此打开一个单独的问题,尽管这可能有帮助.可能你需要做的就是用这个 import * as jQuery from 'jquery';

There are two main problems here. The first is of course that you need to fix your import problem, but that is unrelated to testing. You will need to resolve this before going into testing, and this might have to do with the configuration of your build tool versus running in Node. You should open a separate question for this, although this might be of help. Probably all you need to do is replace the import with this import * as jQuery from 'jquery';

另一个大问题是您在 Node 内部运行它(使用触发 Mocha 的 npm test)而您的代码需要浏览器.Sinon 的假服务器实现旨在用于浏览器环境,而您正在服务器环境中运行测试.这意味着 jQuery 和假服务器设置都不起作用,因为 Node 没有 XHR 对象.

The other big issue is that you are running it inside of Node (using npm test that triggers Mocha) while your code requires a browser. The fake server implementation of Sinon is meant to be used in a browser environment, and you are running the tests in a server environment. That means neither jQuery nor the fake server setup will work, as Node does not have a XHR object.

因此,尽管 Sinon XHR 设置看起来不错,除非您愿意更改测试运行器以在浏览器环境中运行测试 (Karma 非常适合从 CLI 执行此操作!),您需要以其他方式处理此问题.我很少去伪造 XHR,而是在更高级别上剔除依赖项.@CarlMarkham 的回答触及了这一点,但他没有详细说明这将如何与您的代码一起工作.

So although the Sinon XHR setup seems fine, unless you are willing to change your test runner to run your tests inside of a browser environment (Karma is great for doing this from the CLI!), you need to handle this in another way. I seldom reach for faking XHR, and instead I stub out dependencies at a higher level. The answer from @CarlMarkham is touching upon this, but he does not go into details on how this would work with your code.

在 Node 中运行代码时,您基本上只有两个选择:

You are basically left with two options when running your code in Node:

  1. 拦截导入 JQuery 模块并将其替换为您自己的具有 ajax 存根版本的对象的调用.这需要一个模块加载器拦截器,例如 rewireproxyquire.
  2. 直接在您的模块中使用依赖项注入.
  1. Intercept calls that import the JQuery module and replace it with your own object that has a stubbed version of ajax. This requires a module loader intercepter such as rewire or proxyquire.
  2. Use dependency injection directly in your module.

Sinon 主页上有一篇由 Morgan Roderick 撰写的好文章,内容涉及第一个选项,以及几个指向网络上其他地方的其他文章的链接,但不知道怎么做解释了如何做第一个选项.我应该在有时间的时候写一篇......但这里是:

The Sinon homepage has a good article by Morgan Roderick on the first option, as well as several links to other articles elsewhere on the net, but no how to that explains how to do the first option. I should write one when I have time ... but here goes:

在实例级别使用依赖注入

侵入性最小的方法是在您正在测试的实例上公开 ajax 方法.这意味着您不需要向模块本身注入任何内容,也不必考虑事后清理:

The least invasive way is to just expose the ajax method on the instance you are testing. That means you would not need to inject anything into the module itself, and you don't have to think about cleanup afterwards:

// weather.js
export class Weather {
    constructor(latitude, longitude) {
        this.ajax = $.ajax;
        this.latitude  = latitude;
        this.longitude = longitude;
    }

    getWeather() {
        return this.ajax({ ...

// weather.test.js

it('should return a resolved promise if call is successful', (done) => {
    const weather = new Weather(43.65, -79.725);
    const data = '{"coord":{"lon":-79.73, ... }' // fill in
    weather.ajax = createStub(data);

我已经写了关于这个技术的更详细的例子关于Sinon问题 跟踪器.

还有一种方法,它更具侵入性,但可以让您通过直接修改模块的依赖项来保持类代码不变:

There is another way, that is more invasive, but lets you keep the class code unaltered by directly modifying the dependencies of the module:

在模块级别使用依赖注入

只需修改您的 Weather 类,为您的依赖项导出一个 setter 接口,以便它们可以被覆盖:

Just modify your Weather class to export a setter interface for your dependencies so that they can be overwritten:

export const __setDeps(jQuery) => $ = jQuery;

现在您可以将测试简化为如下所示:

Now you can simplify your test to read like this:

import weather from '../js/weather';
const Weather = weather.Weather;

const fakeJquery = {};
weather.__setDeps(fakeQuery);

const createStub = data => () => { promise: Promise.resolve(data) };

it('should return a resolved promise if call is successful', (done) => {
    const weather = new Weather(43.65, -79.725);
    const data = '{"coord":{"lon":-79.73, ... }' // fill in
    fakeQuery.ajax = createStub(data);

    weather.getWeather().then((data) => {
        expect(data.main.temp).to.equal(15.28);
        done();
    });
}

这种方法的一个问题是您正在篡改模块的内部结构,因此您需要恢复 jQuery 对象,以防您需要在其他测试中使用 Wea​​ther 类.你当然也可以做相反的事情:你可以导出 actual jQuery 对象并直接修改 ajax 方法,而不是注入一个假的 jQuery 对象.然后,您将删除上面示例代码中的所有注入代码,并将其修改为类似

One problem with this approach is that you are tampering with the internals of the module, and so you need to restore the jQuery object in case you need to use the Weather class in other tests. You could of course also do the inverse: instead of injecting a fake jQuery object you can export the actual jQuery object and modify the ajax method directly. You would then delete all the injection code in the sample code above and modify it to read something like

// weather.js
export const __getDependencies() => { jquery: $ };


// weather.test.js

it('should return a resolved promise if call is successful', (done) => {
    const weather = new Weather(43.65, -79.725);
    const data = '{"coord":{"lon":-79.73, ... }' // fill in
    
    __getDependencies().jquery.ajax = createStub(data);

     // do test
     
     // restore ajax on jQuery back to its original state

这篇关于如何测试需要 jquery 的 ES6 类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆