18

I am just getting started with the Jest test framework and while straight up unit tests work fine, I am having massive issues testing any component that in its module (ES module via babel+webpack) requires a HTML file.

Here is an example:

import './errorHandler.scss';
import template from './errorHandler.tmpl';

class ErrorHandler {
    ...

I am loading the component specific SCSS file which I have set in Jest's package.json config to return an empty object but when Jest tries to run the import template from './errorHandler.tmpl'; line it breaks saying:

/Users/jannis/Sites/my-app/src/scripts/errorHandler/errorHandler.tmpl.html:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){<div class="overlay--top">
                                                                                         ^
    SyntaxError: Unexpected token <

        at transformAndBuildScript (node_modules/jest-runtime/build/transform.js:284:10)

My Jest config from package.json is as follows:

"jest": {
    "setupTestFrameworkScriptFile": "<rootDir>/test/setupFile.js",
    "moduleDirectories": ["node_modules"],
    "moduleFileExtensions": ["js", "json", "html", "scss"],
    "moduleNameMapper": {
        "^.+\\.scss$": "<rootDir>/test/styleMock.js"
    }
}

It seems that the webpack html-loader is not working correctly with Jest but I can't find any solution on how to fix this.

Does anyone know how I can make these html-loader imports work in my tests? They load my lodash template markup and i'd rather not have these at times massive HTML chunks in my .js file so i can omit the import template from x part.

PS: This is not a react project, just plain webpack, babel, es6.

Jannis
  • 17,025
  • 18
  • 62
  • 75

6 Answers6

25

I encountered this specific problem recently and creating your own transform preprocesser will solve it. This was my set up:

package.json

"jest": {
    "moduleFileExtensions": [
      "js",
      "html"
    ],
    "transform": {
      "^.+\\.js$": "babel-jest",
      "^.+\\.html$": "<rootDir>/test/utils/htmlLoader.js"
    }
 }

NOTE: babel-jest is normally included by default, but if you specify a custom transform preprocessor, you seem to have to include it manually.

test/utils/htmlLoader.js:

const htmlLoader = require('html-loader');

module.exports = {
    process(src, filename, config, options) {
        return htmlLoader(src);
    }
}
bnjmnhndrsn
  • 283
  • 3
  • 8
  • 1
    This was very helpful, thank you. I also want to add that you can do this with `underscore-template-loader` as well. – Allen Aug 22 '17 at 09:07
  • 3
    When I try this I get the error: "a transform's `process` function must return a string, or an object with `code` key containing this string.". Is it possible this only worked with an old version of jest or html-loader? – Sam Hasler Sep 29 '20 at 09:07
  • 2
    @SamHasler yes, "html-loader" became async function, use "html-loader-jest". It does basically the same but requires old "html-loader" version with sync interface. – Dmitrii Dushkin Feb 04 '21 at 10:09
11

A bit late to the party, but wanted to add that there is also this html-loader-jest npm package out there to do this if you wanted to go that route.

Once you npm install it you will add it to your jest configuration with

"transform": {
        "^.+\\.js$": "babel-jest",
        "^.+\\.html?$": "html-loader-jest"
    }
mikeb
  • 10,578
  • 7
  • 62
  • 120
Samuel Mburu
  • 196
  • 1
  • 4
  • Thanks for this, you saved me from hours of investigating! Suddenly using `moduleNameMapper` for html modules led Jest to fail to import `fake-indexeddb`, have no idea why. – Yara_M Jun 29 '21 at 00:01
5

For Jest > 28.x.x with html-loader:

  1. Create a custom transformer as documented here.
jest/html-loader.js
const htmlLoader = require("html-loader");

module.exports = {
  process(sourceText) {
    return {
      code: `module.exports = ${htmlLoader(sourceText)};`,
    };
  },
};
  1. Add it to your jest config.
jest.config.js
...

  // A map from regular expressions to paths to transformers
  transform: {
    "^.+\\.html$": "<rootDir>/jest/html-loader.js",
  },

...

It will fix the error : Invalid return value: process() or/and processAsync() method of code transformer found at "<PATH>" should return an object or a Promise resolving to an object.

WitoldW
  • 749
  • 10
  • 11
  • 2
    I tried this and started getting errors `({"Object.":function(module,exports,require,__dirname,__filename,jest){module.exports = [object Promise]; ^^^^^^^ SyntaxError: Unexpected identifier` – abhishek khandait Jul 02 '22 at 14:03
  • @abhishekkhandait found a solution for it? – Leonardo Rick Sep 19 '22 at 01:44
  • Nope. Unfortunately I had to downgrade to Jest 27. – abhishek khandait Sep 19 '22 at 06:14
  • 2
    @abhishekkhandait oh, that's because the version of html-loader you are using is certainly recent, thus it's returning a Promise instead of the value. It's not compatible. html-loader-jest used to solve that by depending on an older version of html-loader. But it doesn't work with Jest 28+ I've created https://www.npmjs.com/package/jest-html-loader that does the job for Jest 28+ – nicoespeon Jan 12 '23 at 04:20
  • Thank you for making this npm package @nicoespeon ! You saved my hide! – ArcadeRenegade May 13 '23 at 00:03
1

For Jest 28+ you can use jest-html-loader to make Jest work with code that requires HTML files.

npm install --save-dev jest-html-loader

In your jest config, add it as a transformer for .HTML files:

"transform": {
  "^.+\\.html?$": "jest-html-loader"
},
nicoespeon
  • 309
  • 1
  • 8
0

Maybe your own preprocessor file will be the solution:

ScriptPreprocessor

Custom-preprocessors

scriptpreprocessor: The path to a module that provides a synchronous function from pre-processing source files. For example, if you wanted to be able to use a new language feature in your modules or tests that isn't yet supported by node (like, for example, ES6 classes), you might plug in one of many transpilers that compile ES6 to ES5 here.

I created my own preprocessor when I had a problems with my tests after added transform-decorators-legacy to my webpack module loaders.

robi24
  • 576
  • 7
  • 14
0

html-loader-jest doesn't work for me. My workaround for this:

"transform": {
  '\\.(html)$': '<rootDir>/htmlTemplateMock.html'
}

htmlTemplateMock.html is empty file

zucker
  • 1,066
  • 12
  • 26