I am currently trying to write some tests for a node-based GCP Cloud Functions application.
At this point I've stripped it down to the bare minimum:
// index.js
const functions = require("#google-cloud/functions-framework");
const testing = require('#google-cloud/functions-framework/testing');
functions.http("updateProvider",
(req, res) => { res.send("OK"); });
My test file follows the sample here:
// index.spec.js
const {getFunction} = require('#google-cloud/functions-framework/testing');
require('../../');
describe("HelloTests", () => {
test("is testable", () => {
});
});
When I run jest I get the following error:
Cannot find module '#google-cloud/functions-framework/testing' from 'spec/unit/index.spec.js'
Some additional observations:
If I put that import statement into index.js and run the application, it imports just fine.
If I comment out the import statement from index.spec.js but leave it in index.js and run jest, I get the same error for the import in index.js.
This leads me to assume that Jest is not properly handling submodules. I've never worked with submodules like this before (that I can remember), so I'm at a complete loss. I did some digging and this is from the functions-framework node module's package.js:
"exports": {
".": {
"types": "./build/src/index.d.ts",
"default": "./build/src/index.js"
},
"./testing": {
"types": "./build/src/testing.d.ts",
"default": "./build/src/testing.js"
}
},
No idea if this is relevant but wanted to include it in case it's useful.
Any idea why I'm getting this error and/or how to resolve it without switching to ESM?
Update: I switched to ESM and get the exact same error.
This apparently got fixed earlier this year:
Issue: https://github.com/facebook/jest/issues/9771
Initial Release: https://github.com/facebook/jest/releases/tag/v28.0.0-alpha.3
I had copied an older (but still fairly recent!) package.json that was stuck on v27 so it wasn't picking up the latest library. Did a clean install and confirmed no further issue with at least v29.3.1.
Rookie mistake.
I'm trying to run tests using the Mocha.js + JSDOM frameworks, but I'm having trouble getting Mocha to start up. This is in the process of testing a React app using the Vue.js library. I keep getting the following error:
var req = require.context('./', false, /\.vue$/);
TypeError: require.context is not a function
The code in question is:
let req = require.context('./', false, /\.vue$/);
components.forEach(function (component) {
try {
let filePath = './' + component + '.vue';
let injected = inject(req(filePath));
Vue.component(getComponentName(component), injected);
let appComponent = {
name: injected.name,
props: {
autocompletion: {
metadata: getComponentName('template'),
score: xTemplatesScore,
attributes: injected.props || []
}
}
};
appComponents.push(appComponent);
} catch (err) {
console.log(err);
console.error('Vue file was not found for component:' + component + '. Please rename your files accordingly ( component-name.vue )');
}
Is there a way to get around this and actually get Mocha to start up? Or is there a suitable replacement for require.context? I've tried to redo it with just plain string concatenations and a vanilla require, but that keeps telling me that none of the Vue modules can be found.
require.context is a method of webpack. Your tests must be bundled before they can be run.
Normally, you'd create a separate webpack config file for your tests. You'll then create a test bundle using webpack and then run Mocha on this bundle. Alternatively, you can use mocha-loader inside the webpack test config file and let the tests run as part of the bundling process.
Further information can be found in the webpack documentation on testing.
I have a Typescript+Node+Angular2+Electron app and currently trying to run tests for node classes, written also in Typescript.
For building the application and running it within electron I use following tsconfig:
"compilerOptions": {
"module": "system",
"target": "es6",
...
}
So as you can see, it's using systemjs and compiling TS into JS-es6. It works fine, application itself is working.
Now I need Jasmine to come on board. I installed this npm package, updated my gulp tasks to run gulp-jasmine for just 1 file:
gulp.task('jasmine', function() {
gulp.src('./test/test.js')
.pipe(jasmine())
});
This is how my test.js looks like:
System.register(["./models-src/app/models/pathWatch/pathWatch"], function(exports_1, context_1) {
"use strict";
var __moduleName = context_1 && context_1.id;
var pathWatch_1;
return {
setters:[
function (pathWatch_1_1) {
pathWatch_1 = pathWatch_1_1;
}],
execute: function() {
describe("Run Application:", () => {
it("starts", () => {
var pw1 = new pathWatch_1.PathWatch();
expect(true).toEqual(true);
});
});
}
}
});
So, nothing special, 1 import-1test-1assert, wrapped with SystemJs stuff.
When I try to run this test, I have an error: "System is not defined".
My questions are:
1) Is it possible to run jasmine tests, using systemjs loader inside?
2) If it's possible, do I need to install/configure some additional stuff?
3) I tried to compile TS using Module="commonjs" and it's working. But I don't want to compile my source differently for tests and build. Why it's working fine with commonjs without any additional manipulations?
4) Also I tried to compile TS using Module="es6". It's not working, I have an error "Unexpected reserved word". Is it possible to run jasmine tests written in js es6 without transpiling them into es5?
Thanks a lot!
1) Is it possible to run jasmine tests, using systemjs loader inside?
2) If it's possible, do I need to install/configure some additional
stuff?
You mean, run jasmine tests in node using systemjs as a loader? I don't think jasmine supports using systemjs instead of require for loading modules. So your tests need to be in commonjs, but test code can use SystemJS to load and test application code. Something like this in test.js could work, provided that systemjs is configured properly and can find pathWatch module:
describe("Run Application:", () => {
it("starts", (done) => {
var system = require('systemjs');
system.config({
// systemjs config here
//
});
system.import('path-to-path-watch-module').then(pathWatch => {
var pw = new pathWatch.PathWatch();
expect(true).toEqual(true);
done();
});
});
});
system.import is asynchronous, so all jasmine tests need to be async too.
3) I tried to compile TS using Module="commonjs" and it's working. But
I don't want to compile my source differently for tests and build. Why
it's working fine with commonjs without any additional manipulations?
Because then there is no reference to System in the compiled code - it uses module.exports like any other node module and can be loaded as is by jasmine.
4) Also I tried to compile TS using Module="es6". It's not working, I
have an error "Unexpected reserved word". Is it possible to run
jasmine tests written in js es6 without transpiling them into es5?
Module="es6" requires a runtime that supports es6 import and export, so it needs a transpiler and module loader before it can run on current version of node.
Suppose I have the following module:
var modulesReq = require.context('.', false, /\.js$/);
modulesReq.keys().forEach(function(module) {
modulesReq(module);
});
Jest complains because it doesn't know about require.context:
FAIL /foo/bar.spec.js (0s)
● Runtime Error
- TypeError: require.context is not a function
How can I mock it? I tried using setupTestFrameworkScriptFile Jest configuration but the tests can't see any changes that I've made in require.
I had the same problem, then I've made a 'solution'.
I'm pretty sure that this is not the best choice. I ended up stopping using it, by the points answered here:
https://github.com/facebookincubator/create-react-app/issues/517
https://github.com/facebook/jest/issues/2298
But if you really need it, you should include the polyfill below in every file that you call it (not on the tests file itself, because the require will be no global overridden in a Node environment).
// This condition actually should detect if it's an Node environment
if (typeof require.context === 'undefined') {
const fs = require('fs');
const path = require('path');
require.context = (base = '.', scanSubDirectories = false, regularExpression = /\.js$/) => {
const files = {};
function readDirectory(directory) {
fs.readdirSync(directory).forEach((file) => {
const fullPath = path.resolve(directory, file);
if (fs.statSync(fullPath).isDirectory()) {
if (scanSubDirectories) readDirectory(fullPath);
return;
}
if (!regularExpression.test(fullPath)) return;
files[fullPath] = true;
});
}
readDirectory(path.resolve(__dirname, base));
function Module(file) {
return require(file);
}
Module.keys = () => Object.keys(files);
return Module;
};
}
With this function, you don't need to change any require.context call, it will execute with the same behavior as it would (if it's on webpack it will just use the original implementation, and if it's inside Jest execution, with the polyfill function).
After spending some hours trying each of the answers above. I would like to contribute.
Adding babel-plugin-transform-require-context plugin to .babelrc for test env fixed all the issues.
Install - babel-plugin-transform-require-context here https://www.npmjs.com/package/babel-plugin-transform-require-context (available with yarn too)
Now add plugin to .babelrc
{
"env": {
"test": {
"plugins": ["transform-require-context"]
}
}
}
It will simply transform require-context for test env into dummy fn calls so that code can run safely.
If you are using Babel, look at babel-plugin-require-context-hook. Configuration instructions for Storybook are available at Storyshots | Configure Jest to work with Webpack's require.context(), but they are not Storyshots/Storybook specific.
To summarise:
Install the plugin.
yarn add babel-plugin-require-context-hook --dev
Create a file .jest/register-context.js with the following contents:
import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
registerRequireContextHook();
Configure Jest (the file depends on where you are storing your Jest configuration, e.g. package.json):
setupFiles: ['<rootDir>/.jest/register-context.js']
Add the plugin to .babelrc
{
"presets": ["..."],
"plugins": ["..."],
"env": {
"test": {
"plugins": ["require-context-hook"]
}
}
}
Alternatively, add it to babel.config.js:
module.exports = function(api) {
api.cache(true)
const presets = [...]
const plugins = [...]
if (process.env.NODE_ENV === "test") {
plugins.push("require-context-hook")
}
return {
presets,
plugins
}
}
It may be worth noting that using babel.config.js rather than .babelrc may cause issues. For example, I found that when I defined the require-context-hook plugin in babel.config.js:
Jest 22 didn't pick it up;
Jest 23 picked it up; but
jest --coverage didn't pick it up (perhaps Istanbul isn't up to speed with Babel 7?).
In all cases, a .babelrc configuration was fine.
Remarks on Edmundo Rodrigues's answer
This babel-plugin-require-context-hook plugin uses code that is similar to Edmundo Rodrigues's answer here. Props to Edmundo! Because the plugin is implemented as a Babel plugin, it avoids static analysis issues. e.g. With Edmundo's solution, Webpack warns:
Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
Despite the warnings, Edmundo's solution is the most robust because it doesn't depend on Babel.
Extract the call to a separate module:
// src/js/lib/bundle-loader.js
/* istanbul ignore next */
module.exports = require.context('bundle-loader?lazy!../components/', false, /.*\.vue$/)
Use the new module in the module where you extracted it from:
// src/js/lib/loader.js
const loadModule = require('lib/bundle-loader')
Create a mock for the newly created bundle-loader module:
// test/unit/specs/__mocks__/lib/bundle-loader.js
export default () => () => 'foobar'
Use the mock in your test:
// test/unit/specs/lib/loader.spec.js
jest.mock('lib/bundle-loader')
import Loader from 'lib/loader'
describe('lib/loader', () => {
describe('Loader', () => {
it('should load', () => {
const loader = new Loader('[data-module]')
expect(loader).toBeInstanceOf(Loader)
})
})
})
Alrighty! I had major issues with this and managed to come to a solution that worked for me by using a combination of other answers and the Docs. (Took me a good day though)
For anyone else who is struggling:
Create a file called bundle-loader.js and add something like:
module.exports = {
importFiles: () => {
const r = require.context(<your_path_to_your_files>)
<your_processing>
return <your_processed_files>
}
}
In your code import like:
import bundleLoader from '<your_relative_Path>/bundle-loader'
Use like
let <your_var_name> = bundleLoader.importFiles()
In your test file right underneath other imports:
jest.mock('../../utils/bundle-loader', () => ({
importFiles: () => {
return <this_will_be_what_you_recieve_in_the_test_from_import_files>
}
}))
Installing
babel-plugin-transform-require-context
package and adding the plugin in the .babelrc resolved the issue for me.
Refer to the documentation here:
https://www.npmjs.com/package/babel-plugin-transform-require-context
The easiest and fastest way to solve this problem will be to install require-context.macro
npm install --save-dev require-context.macro
then just replace:
var modulesReq = require.context('.', false, /\.js$/);
with:
var modulesReq = requireContext('.', false, /\.js$/);
Thats it, you should be good to go!
Cheers and good luck!
Implementation problems not mentioned:
Jest prevents out-of-scope variables in mock, like __dirname.
Create React App limits Babel and Jest customization. You need to use src/setupTests.js which is run before every test.
fs is not supported in the browser. You will need something like browserFS. Now your app has file system support, just for dev.
Potential race condition. Export after this import. One of your require.context imports includes that export. I'm sure require takes care of this, but now we are adding a lot of fs work on top of it.
Type checking.
Either #4 or #5 created undefined errors. Type out the imports, no more errors. No more concerns about what can or can't be imported and where.
Motivation for all this? Extensibility. Keeping future modifications limited to one new file. Publishing separate modules is a better approach.
If there's an easier way to import, node would do it. Also this smacks of premature optimization. You end up scrapping everything anyways because you're now using an industry leading platform or utility.
If you're using Jest with test-utils in Vue.
Install these packages:
#vue/cli-plugin-babel
and
babel-plugin-transform-require-context
Then define babel.config.js at the root of the project with this configuration:
module.exports = function(api) {
api.cache(true);
const presets = [
'#vue/cli-plugin-babel/preset'
];
const plugins = [];
if (process.env.NODE_ENV === 'test') {
plugins.push('transform-require-context');
}
return {
presets,
plugins
};
};
This will check if the current process is initiated by Jest and if so, it mocks all the require.context calls.
I faced the same issue with an ejected create-react-app project
and no one from the answers above helped me...
My solution were to copy to config/babelTransform.js the follwoing:
module.exports = babelJest.createTransformer({
presets: [
[
require.resolve('babel-preset-react-app'),
{
runtime: hasJsxRuntime ? 'automatic' : 'classic',
},
],
],
plugins:["transform-require-context"],
babelrc: false,
configFile: false,
});
Simpleset Solution for this
Just Do
var modulesReq = require.context && require.context('.', false, /\.js$/);
if(modulesReq) {
modulesReq.keys().forEach(function(module) {
modulesReq(module);
});
}
So Here I have added extra check if require.context is defined then only execute By Doing this jest will no longer complain
I have a project being built with webpack. This allows me to import .svg files to create React components.
When running tests I have been attempting to avoid using webpack to avoid tying the mocha version to a webpack plugin. Unfortunately, when the .svg imports are hit, they fail to be found. We are also using css modules, and they allowed me to use the css-modules-require-hook to work around the importing of css files.
Is there a technique I could use to accomplish the same thing with SVGs?
I saw this solved by using require.extensions (in node this is deprecated, but should never go away) to force importing these types of assets to result in an no-op. Starting mocha with the --require flag lets us configure the runtime environment for our tests so starting mocha like this:
NODE_PATH=./app mocha -w --compilers js:babel-core/register --require ./app/lib/testHelper.js --require ./app/lib/testNullCompiler.js 'app/**/*.spec.#(js|jsx)' --watch-extensions js,jsx
where testNullCompiler.js is:
const noop = () => 1;
require.extensions['.css'] = noop;
require.extensions['.scss'] = noop;
require.extensions['.png'] = noop;
require.extensions['.jpg'] = noop;
require.extensions['.jpeg'] = noop;
require.extensions['.gif'] = noop;
require.extensions['.svg'] = noop;
This will cause all the above types of files to return the noop function instead of attempting to load the actual file.
My code is using the es6 import syntax but I'm assuming that babel is converting that to require under the covers which enables this technique to work.
Yes, I just find a way to mock .svg module for mocha here.
Mocha can use require hooks to deal with universal javascript import, such as babel-register for .js.
You can use require-hacker to add hooks to extensions of modules.
You can use a null react component mock for other component loaders such as react-svg-loader to load .svg as a react component.
here is the example:
import requireHacker from 'require-hacker';
const fakeComponentString = `
require('react').createClass({
render() {
return null;
}
})
`;
// for fake component
requireHacker.hook('svg', path => `module.exports = ${fakeComponentString}`);