如何测试需要jquery的ES6类?

时间:2017-06-06 18:47:37

标签: javascript ecmascript-6 mocha sinon

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

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模块中使用时工作正常,但问题在于我正在为它编写的测试。

以下是测试:

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来运行测试。

什么时候npm test,我收到此错误:

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,为什么会发生这种情况?

4 个答案:

答案 0 :(得分:2)

这里有两个主要问题。第一个当然是您需要修复导入问题,但这与测试无关。在进行测试之前,您需要解决此问题,这可能与构建工具的配置相比,而不是在Node中运行。你应该为此开一个单独的问题,although this might be of help。您可能需要做的就是使用此import * as jQuery from 'jquery';

替换导入

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

因此,尽管Sinon XHR设置似乎很好,除非您愿意更改测试运行器以在浏览器环境中运行测试(Karma非常适合从CLI执行此操作!),您需要以另一种方式处理这个。我很少涉及伪造XHR,而是在更高层次上存在依赖关系。 @CarlMarkham的答案正在触及这一点,但他没有详细说明这将如何与您的代码一起使用。

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

  1. 拦截导入JQuery模块的调用,并将其替换为您自己的具有存根版本ajax的对象。这需要模块加载器拦截器,例如rewireproxyquire
  2. 直接在模块中使用依赖注入。
  3. Sinon主页在第一个选项上有a good article by Morgan Roderick,还有几个links to other articles elsewhere on the net,但没有解释如何解释如何执行第一个选项。我有空的时候应该写一个...但是这里有:

    在实例级别使用依赖项注入

    最少侵入性的方法是在您正在测试的实例上公开ajax方法。这意味着您不需要向模块本身注入任何东西,之后您不必考虑清理:

    // 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);
    

    还有另外一种方法,那就是更具侵略性,但是让你通过直接修改模块的依赖关系来保持类代码不变:

    在模块级别使用依赖项注入

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

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

    现在,您可以将测试简化为以下内容:

    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对象,以防你需要在其他测试中使用Weather类。你当然也可以做反过来:你可以导出实际 jQuery对象而不是注入假的jQuery对象,直接修改ajax方法。然后,您将删除上面示例代码中的所有注入代码,并将其修改为读取类似

    的内容
    // 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
    

答案 1 :(得分:0)

除非是导入default导出的ES6模块(jQuery不导出),否则导入它的正确方法是

import * as $ from 'jquery';

使用import处理CommonJS模块的方式是构建工具的责任。至少在较旧的Webpack版本中,(错误地)支持非ES6模块的default导入。

没有版本限制
"webpack": "*"

依赖是破坏构建的直接方法。

答案 2 :(得分:0)

在我的项目中,我存储了我使用的所有jQuery方法,因此,我将存根$.ajax,因为您需要测试的只是返回的promise。

在另一个档案中有这样的事情:

module.exports = {
  ajax: sinon.stub(global.$, 'ajax')
}

然后,在你的测试中

import { ajax } from 'stubs'

这样,ajax方法是存根的,您可以在每个测试的基础上定义它应返回的内容:

ajax.returns({
  done: (callback) => { callback(someParam) },
  fail: () => {},
  always: (callback) => { callback() }
});

答案 3 :(得分:0)

我最近遇到了类似的问题。我发现,当我使用mocha和webpack进行测试时,jquery绑定的范围内没有“窗口”,因此它是未定义的。为了解决此问题,我发现我可以遵循this advice并将源文件中的GET替换为:

import * as $ from jquery

但是随后源文件将不再与webpack捆绑在一起,因此我放弃了使用mocha和babel进行客户端javascript测试。相反,我发现可以使用karma和phantomjs的组合来正确测试客户端代码。

首先,我安装了所有依赖项:

// not the winning solution
const jsdom = require("jsdom");
const { window } = new jsdom.JSDOM();
const $ = require('jquery')(window);

然后我使用以下命令在名为npm install -D babel-loader @babel/core npm install -D mocha chai sinon mocha-webpack npm install -D phantomjs-prebuilt npm install -D webpack npm install -D karma karma-mocha karma-chai karma-sinon npm install -D karma-mocha-reporter karma-phantomjs-launcher karma-webpack 的根目录中设置一个配置文件:

karma.config.js

最后,我在package.json中的脚本中添加了module.exports = function(config) { config.set({ browsers: ['PhantomJS'], files: [ './test/spec/*.js' ], frameworks: ['mocha', 'chai', 'sinon'], reporters: ['mocha'], preprocessors: { './test/spec/*.js': ['webpack'] }, webpack: { module: { rules: [ { test: /\.js/, exclude: /node_modules/, loader: 'babel-loader' } ] }, watch: true, mode: 'none' }, webpackServer: { noInfo: true }, singleRun: true }); }; 。现在,所有规格测试都可以使用"test": "karma start karma.config.js"运行。