我有一个需要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
,为什么会发生这种情况?
这里有两个主要问题。第一个当然是您需要修复导入问题,但这与测试无关。在进行测试之前,您需要解决此问题,这可能与构建工具的配置相比,而不是在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中运行代码时,基本上有两个选项:
ajax
。这需要一个模块加载器拦截器,如rewire
或proxyquire
。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
除非是ES6模块具有导入的default
导出(jQuery不是),导入它的正确方法是
import * as $ from 'jquery';
使用import
处理CommonJS模块的方式是构建工具的责任。至少在较旧的Webpack版本中,(错误地)支持非ES6模块的default
导入。没有版本限制
"webpack": "*"
依赖是破坏构建的直接方法。
在我的项目中,我存储了我使用的所有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() }
});
我最近遇到了类似的问题。我发现当我使用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
运行所有规范测试。