I'm looking for help with unit tests for my app, where I'm using indexedDB. Before I implemented indexedDB functionality, tests were correct. But now, for all of them I see one error:
ReferenceError: indexedDB is not defined
Can someone give me an advice how to get rid of that error? I was searching information, and trying different ways to mock window, or indexedDB, but with no result.
This issue is due to Dexie expecting window.indexedDB to be defined, this is not the case when running in a headless mode (using Jest) that does not have a true DOM or window scope.
Found a solution deep in the Dexie git issues which suggests:
const Dexie = require('dexie')
Dexie.dependencies.indexedDB = require('fake-indexeddb')
Dexie.dependencies.IDBKeyRange = require('fake-indexeddb/lib/FDBKeyRange')
We have also had success with:
import Dexie from 'dexie';
import indexedDB from 'fake-indexeddb';
Dexie.dependencies.indexedDB = indexedDB;
Link to the original issue:
https://github.com/dfahlander/Dexie.js/issues/495
Or according to the documentation, you can provide the indexedDB option like:
import Dexie from 'dexie';
import indexedDB from 'fake-indexeddb';
var db = new Dexie("MyDatabase", { indexedDB: indexedDB });
Link to documentation: http://dexie.org/docs/Dexie/Dexie
If You are using jest and enzyme for testing indexdb or you are using dexie which is a indexDB wrapper which is also used for implementing indexDB api you have to just add these three lines in you global-test.js file .
const Dexie = require('dexie');
Dexie.dependencies.indexedDB = require('fake-indexeddb');
Dexie.dependencies.IDBKeyRange = require('fake-indexeddb/lib/FDBKeyRange');
Now you have to provide this file to jest, show that it can use fake-indexddb instead of original indexDB.
setupFiles: ['<rootDir>/src/test/globals-test.ts']
when using jest, according to the fakeindexeddb docs,
install,
npm install --save-dev fake-indexeddb
or
yarn add --dev fake-indexeddb
then add below code to the jestconfig file
"jest": {
...
"setupFiles": [
"fake-indexeddb/auto"
]
}
I'm not using Dexie (but instead got here when Firebase was throwing an exception on import), the fix was simply adding require('fake-indexeddb/auto') into setupTests.ts for Jest to pick up.
For Angular 7.3+ with jest add this to your global-test.ts file:
const Dexie = require('dexie');
Dexie.dependencies.indexedDB = require('fake-indexeddb');
Dexie.dependencies.IDBKeyRange = require('fake-indexeddb/lib/FDBKeyRange');
then load the file in jest.config.js:
module.exports = {
preset: 'jest-preset-angular',
transformIgnorePatterns: ['node_modules'],
setupTestFrameworkScriptFile: '<rootDir>/src/setupJest.ts',
moduleNameMapper: {
'\\.(jpg|jpeg|png)$': '<rootDir>/__mocks__/image.js',
'#lib/(.*)': '<rootDir>/src/lib/$1'
},
globals: {
'ts-jest': {
tsConfigFile: 'src/tsconfig.spec.json'
},
__TRANSFORM_HTML__: true
},
setupFiles: ['<rootDir>/src/test/globals-test.ts']
};
Related
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.
In cypress migration guide they mention that plugin files are no longer supported. They also mention that you need to use >=v3.10 of code-coverage plugin
I do have correct version installed, and I tried to update cypress.config.ts to:
import { defineConfig } from "cypress";
export default defineConfig({
video: false,
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
require('#cypress/code-coverage/task')(on, config)
return config
},
},
});
but it doesn't work. What's the correct way? I believe I do have instrumenting working (I'm using #cypress/instrument-cra and see coverage object) but I don't see generated coverage files and I don't see a reset coverege step in tests
From your description the only step missing is the support file import.
// cypress/support/e2e.js
import '#cypress/code-coverage/support'
I also have a .babelrc with the following, but I believe you can do without it if just covering e2e tests.
{
"plugins": ["istanbul"]
}
Let me know if that's not it, I will give you step-by-step.
I have a Vue component that renders an Xterm.js terminal.
Terminal.vue
<template>
<div id="terminal"></div>
</template>
<script>
import Vue from 'vue';
import { Terminal } from 'xterm/lib/public/Terminal';
import { ITerminalOptions, ITheme } from 'xterm';
export default Vue.extend({
data() {
return {};
},
mounted() {
Terminal.applyAddon(fit);
this.term = new Terminal(opts);
this.term.open(document.getElementById('terminal'));
},
</script>
I would like to test this component.
Terminal.test.js
import Terminal from 'components/Terminal'
import { mount } from '#vue/test-utils';
describe('test', ()=>{
const wrapper = mount(App);
});
When I run jest on this test file, I get this error:
TypeError: Cannot set property 'globalCompositeOperation' of null
45 | this.term = new Terminal(opts);
> 46 | this.term.open(document.getElementById('terminal'));
Digging into the stack trace, I can see it has something to do with Xterm's ColorManager.
at new ColorManager (node_modules/xterm/src/renderer/ColorManager.ts:94:39)
at new Renderer (node_modules/xterm/src/renderer/Renderer.ts:41:25)
If I look at their code, I can see a relatively confusing thing:
xterm.js/ColorManager.ts
constructor(document: Document, public allowTransparency: boolean) {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
// I would expect to see the "could not get rendering context"
// error, as "ctx" shows up as "null" later, guessing from the
// error that Jest caught
if (!ctx) {
throw new Error('Could not get rendering context');
}
this._ctx = ctx;
// Somehow this._ctx is null here, but passed a boolean check earlier?
this._ctx.globalCompositeOperation = 'copy';
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
this.colors = {
foreground: DEFAULT_FOREGROUND,
background: DEFAULT_BACKGROUND,
cursor: DEFAULT_CURSOR,
cursorAccent: DEFAULT_CURSOR_ACCENT,
selection: DEFAULT_SELECTION,
ansi: DEFAULT_ANSI_COLORS.slice()
};
}
I'm not quite clear on how canvas.getContext apparently returned something that passed the boolean check (at if(!ctx)) but then later caused a cannot set globalCompositeOperation of null error on that same variable.
I'm very confused about how I can successfully go about mock-rendering and thus testing this component - in xterm's own testing files, they appear to be creating a fake DOM using jsdom:
xterm.js/ColorManager.test.ts
beforeEach(() => {
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
(<any>window).HTMLCanvasElement.prototype.getContext = () => ({
createLinearGradient(): any {
return null;
},
fillRect(): void { },
getImageData(): any {
return {data: [0, 0, 0, 0xFF]};
}
});
cm = new ColorManager(document, false);
});
But I believe that under the hood, vue-test-utils is also creating a fake DOM using jsdom. Furthermore, the documentation indicates that the mount function both attaches and renders the vue component.
Creates a Wrapper that contains the mounted and rendered Vue component.
https://vue-test-utils.vuejs.org/api/#mount
How can I successfully mock a DOM in such a way that I can test a Vue component that implements Xterm.js, using Jest?
There are multiple reasons for this.
First of all, Jest js uses jsdom under the hood, as I suspected.
Jsdom doesn't support the canvas DOM api out of the box. First of all, you need jest-canvas-mock.
npm install --save-dev jest-canvas-mock
Then, you need to add it to the setupFiles portion of your jest config. Mine was in package.json, so I added it like so:
package.json
{
"jest": {
"setupFiles": ["jest-canvas-mock"]
}
}
Then, I was getting errors about the insertAdjacentElement DOM element method. Specifically, the error was:
[Vue warn]: Error in mounted hook: "TypeError: _this._terminal.element.insertAdjacentElement is not a function"
This is because the version of jsdom used by jest is, as of today, 11.12.0 :
npm ls jsdom
└─┬ jest#24.8.0
└─┬ jest-cli#24.8.0
└─┬ jest-config#24.8.0
└─┬ jest-environment-jsdom#24.8.0
└── jsdom#11.12.0
Through the help of stackoverflow, I discovered that at version 11.12.0, jsdom had not implemented insertAdjacentElement. However, a more recent version of jsdom implemented insertAdjacentElement back in July of 2018.
Efforts to convince the jest team to use a more up to date version of jsdom have failed. They are unwilling to let go of node6 compatibility until the absolute last second (they claimed back in April), or alternatively don't want to implement jsdom at all anymore, and are recommending people fork their own versions of the repo if they want the feature.
Luckily, you can manually set which version of jsdom jest uses.
First, install the jest-environment-jsdom-fourteen package.
npm install --save jest-environment-jsdom-fourteen
Then, you need to modify the testEnvironment property of your jest config. So, now my jest config looks like:
package.json
"jest": {
"testEnvironment": "jest-environment-jsdom-fourteen",
"setupFiles": ["jest-canvas-mock"]
}
Now, I can run tests without errors.
Great answer above which was going to be my solution but since I'm using react-scripts I didn't really want to eject (testEnvironment is not supported config setting). So I had a look around in source code of react-scripts how I can potentially sneak in and override testEnvironment.
https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/scripts/test.js
Line 124 looks quite interesting
resolvedEnv = resolveJestDefaultEnvironment(`jest-environment-${env}`);
So a brilliant idea came to my mind, no pun intended, to stick --env=jsdom-fourteen as command line arg. My CI command looks like this now
cross-env CI=true react-scripts test --coverage --env=jsdom-fourteen --testResultsProcessor=jest-teamcity-reporter
and it miraculously works :).
I also have setupTests.js file in src folder where I import jest-canvas-mock and jest-environment-jsdom-fourteen but before the --env hacky option the tests were spitting out the insertAdjacentElement mentioned above.
Obvs this is very hacky and it will break at some point but it's fine for now, hopefully Jest will start supporting JSDOM 14 soon.
I'm trying to create a custom transform for Jest, but running into a documentation roadblock which has me asking myself if I'm even on the right track.
Problem
I have a Rails project which is serving a Vue JS app. I want to write Jest tests for the JS app. In order to pass config variables from Rails to the app, I'm using ERB to template a small number of .js files. For example:
// in server-routes.js.erb
export default {
reports: '<%= Rails.application.config.relative_url_root %><%= Rails.application.routes.url_helpers.reports_path %>',
...
In my Webpack build for the Vue app, I use rails-erb-loader to preprocess the *.erb files before they get passed to the rest of the build process.
However, when I run my JS tests, Jest doesn't know anything about ERB loaders (reasonably enough). So my goal is to add a custom transform for Jest to convert the ERB files when running npm test.
Approach
I thought I might be able to use rails-erb-loader as a Jest transform:
// package.json
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"moduleDirectories": [
"<rootDir>/node_modules"
],
"transform": {
".*\\.(vue)$": "vue-jest",
"^.+\\.js$": "babel-jest",
"^.+\\.js\\.erb$": "rails-erb-loader"
},
This doesn't work, however, because Jest transforms and Webpack loaders seemingly have different signatures. In particular, Jest expects a process function:
$ npm test
FAIL app/javascript/components/__tests__/dummy.test.js
● Test suite failed to run
TypeError: Jest: a transform must export a `process` function.
> 101 | import ServerRoutes from '../server-routes.js.erb';
| ^
at ScriptTransformer._getTransformer (node_modules/#jest/transform/build/ScriptTransformer.js:291:15)
at ScriptTransformer.transformSource (node_modules/#jest/transform/build/ScriptTransformer.js:353:28)
at ScriptTransformer._transformAndBuildScript (node_modules/#jest/transform/build/ScriptTransformer.js:457:40)
at ScriptTransformer.transform (node_modules/#jest/transform/build/ScriptTransformer.js:513:25)
at app/javascript/components/related-media.vue:101:1
at Object.<anonymous> (app/javascript/components/related-media.vue:232:3)
And this is where I get stuck, because I can't see where it's documented what the API and behaviour of a process function should be. In the documentation for the transform config option there's a single not very helpful example, and that's it as far as docs go, unless I've missed something.
I also note that babel-jest has a createTransformer function which sounds like it might be helpful, or at least illuminating, but again I can't find any docs on what it does.
If anyone has pointers on the details of creating custom Jest transforms, or at least some better docs, that would be great! Or, if I'm going about this the wrong way, what should I be doing?
You could look at ts-jest. https://github.com/kulshekhar/ts-jest/blob/master/src/ts-jest-transformer.ts. It is in typescript so is typed
Alternatively find the jest code that initiates the transform process. I don't think it is that difficult to find.
I think the transformer is either created with class constructor or via the factory function createTransformer.
From my understanding for the ts-jest-transformer and jest-erb-transformer it seems you need to export an object with public process or to export createTransformer methods which create a transformer object that have a process method.
a simple code example that runs before ts-jest
transform-example.js
const tsJest = require('ts-jest');
const t = tsJest.createTransformer();
module.exports = {
process(fileContent, filePath, jestConfig) {
const res = t.process(fileContent, filePath, jestConfig)
console.log(filePath);
return res;
}
}
jest.config.js
module.exports = {
transform: {
'^.+\\.tsx?$': ['<rootDir>/transform-example']
}
}
running this would run typescript tests (just like ts-jest) and log all the file paths transformed in the test.
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