How can I use my webpack's html-loader imports in Jest tests?
Asked Answered
G

6

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.

Genro answered 14/9, 2016 at 6:35 Comment(0)
A
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);
    }
}
Actomyosin answered 9/1, 2017 at 22:27 Comment(3)
This was very helpful, thank you. I also want to add that you can do this with underscore-template-loader as well.Considerate
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?Verboten
@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.Bermuda
F
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"
    }
Folks answered 13/3, 2019 at 14:38 Comment(1)
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.Willhite
B
6

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.

Bulb answered 15/6, 2022 at 17:16 Comment(5)
I tried this and started getting errors ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){module.exports = [object Promise]; ^^^^^^^ SyntaxError: Unexpected identifierConsider
@abhishekkhandait found a solution for it?Batik
Nope. Unfortunately I had to downgrade to Jest 27.Consider
@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 npmjs.com/package/jest-html-loader that does the job for Jest 28+ 👍Rawdin
Thank you for making this npm package @Rawdin ! You saved my hide!Odericus
R
3

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"
},
Rawdin answered 12/1, 2023 at 4:23 Comment(0)
S
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.

Sherrilsherrill answered 19/9, 2016 at 11:52 Comment(0)
S
0

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

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

htmlTemplateMock.html is empty file

Supernormal answered 4/6, 2021 at 11:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.