I have an ES6 module that needs jquery.
import $ from \'jquery\';
export class Weather {
/**
* Constructor for Weather class
*
* @param
Unless it is ES6 module that has default
export that is being imported (which jQuery is not), the proper way to import it is
import * as $ from 'jquery';
The way how CommonJS modules are treated with import
is the responsibility of build tool. default
imports for non-ES6 modules was (erroneously) supported at least in older Webpack versions. And no version restriction for
"webpack": "*"
dependency is a direct way to broken builds.
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';
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.
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.
You are basically left with two options when running your code in Node:
ajax
. This requires a module loader intercepter such as rewire
or proxyquire
.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:
Using dependency injection on the instance level
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);
There is another way, that is more invasive, but lets you keep the class code unaltered by directly modifying the dependencies of the module:
Using dependency injection on the module level
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();
});
}
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
In my project, I stub all the jQuery methods I use, so, I would stub $.ajax
since all you need to test are the returned promises.
Something like this in another file:
module.exports = {
ajax: sinon.stub(global.$, 'ajax')
}
Then, in your test
import { ajax } from 'stubs'
This way, the ajax
method is stubbed and you can define what it should return on a per test basis:
ajax.returns({
done: (callback) => { callback(someParam) },
fail: () => {},
always: (callback) => { callback() }
});
I ran into a similar problem recently. I found that when I ran a test with mocha and webpack, there was no 'window' in the scope for jquery to bind to and as a result it was undefined. To solve this problem, I found that I could follow this advice and replace import * as $ from jquery
in the source file with:
// not the winning solution
const jsdom = require("jsdom");
const { window } = new jsdom.JSDOM();
const $ = require('jquery')(window);
But then the source file would no longer properly bundle with webpack, so I gave up on using mocha and babel for client-side javascript tests. Instead, I found that I could test my client-side code properly using a combination of karma and phantomjs.
First, I installed all the dependencies:
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
Then I setup a config file in the root called karma.config.js
with:
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
});
};
Finally, I added "test": "karma start karma.config.js"
to scripts in package.json. All the spec tests can now be run with npm test
.