如何测试需要jquery的ES6类?

问题描述 投票:2回答:4

我有一个需要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,为什么会发生这种情况?

javascript ecmascript-6 mocha sinon
4个回答
2
投票

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

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

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

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

  1. 拦截调用导入JQuery模块并将其替换为您自己的对象,该对象具有存根版本的ajax。这需要一个模块加载器拦截器,如rewireproxyquire
  2. 直接在模块中使用依赖注入。

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对象并直接修改ajax方法,而不是注入假的jQuery对象。然后,您将删除上面示例代码中的所有注入代码,并将其修改为类似的内容

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

0
投票

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

import * as $ from 'jquery';

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

"webpack": "*"

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


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

0
投票

我最近遇到了类似的问题。我发现当我使用mocha和webpack运行测试时,jquery绑定到的范围内没有“窗口”,因此它未定义。为了解决这个问题,我发现我可以跟随this advice并在源文件中替换import * as $ from jquery

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

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

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

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的配置文件:

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"添加到package.json中的脚本中。现在可以使用npm test运行所有规范测试。

© www.soinside.com 2019 - 2024. All rights reserved.