36

I'm using a TensorFlow encoder in my application. It works fine in my browser when the application is running but I get issues when testing that it builds:

$ npx react-scripts test --env=jsdom
FAIL  src/App.test.js
  ● Test suite failed to run

    ReferenceError: TextEncoder is not defined

      16 | import TextField from '@material-ui/core/TextField';
      17 | import Typography from '@material-ui/core/Typography';
    > 18 | import * as mobilenet from '@tensorflow-models/mobilenet';
         | ^
      19 | import * as UniversalSentenceEncoder from '@tensorflow-models/universal-sentence-encoder';
      20 | import * as tf from '@tensorflow/tfjs';
      21 | import axios from 'axios';

      at new PlatformBrowser (node_modules/@tensorflow/tfjs-core/src/platforms/platform_browser.ts:26:28)
      at Object.<anonymous> (node_modules/@tensorflow/tfjs-core/src/platforms/platform_browser.ts:50:30)
      at Object.<anonymous> (node_modules/@tensorflow/tfjs-core/src/index.ts:29:1)
      at Object.<anonymous> (node_modules/@tensorflow/tfjs-converter/src/executor/graph_model.ts:18:1)
      at Object.<anonymous> (node_modules/@tensorflow/tfjs-converter/src/index.ts:17:1)
      at Object.<anonymous> (node_modules/@tensorflow-models/mobilenet/dist/index.js:38:14)
      at Object.<anonymous> (src/components/model.js:18:1)
      at Object.<anonymous> (src/App.js:8:1)
      at Object.<anonymous> (src/App.test.js:3:1)

I'd like to get rid of that error. I've tried using the 'text-encoding' package but I'm not sure how get TextEncoder properly defined before the import happens.

Maybe I can set a different option for --env?

I get the same error without --env=jsdom. I believe I added it after getting similar types of not defined errors and it corrected an issue.

Here is my test:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
  ReactDOM.unmountComponentAtNode(div);
});

So setting --env=node does not work either because: ReferenceError: document is not defined.

Justin Harris
  • 1,969
  • 2
  • 23
  • 33
  • 1
    The solution, polyfill encoder globally, provided in a similar question solved it for me https://stackoverflow.com/a/68468204/5404186 – David Dal Busco Jan 11 '22 at 08:11

12 Answers12

41

jsdom Doesn't seem to have TextEncoder defined in global for the DOM. So you can fill it in with the node.js one.

test/custom-test-env.js:

const Environment = require('jest-environment-jsdom');

/**
 * A custom environment to set the TextEncoder that is required by TensorFlow.js.
 */
module.exports = class CustomTestEnvironment extends Environment {
    async setup() {
        await super.setup();
        if (typeof this.global.TextEncoder === 'undefined') {
            const { TextEncoder } = require('util');
            this.global.TextEncoder = TextEncoder;
        }
    }
}

npx react-scripts test --env=./test/custom-test-env.js

Justin Harris
  • 1,969
  • 2
  • 23
  • 33
Phoenix
  • 612
  • 6
  • 8
  • Thanks! I made it a little future proof by removing the `require('util')` at the top and using: `if (typeof TextEncoder === 'undefined') { const { TextEncoder } = require('util'); this.global.TextEncoder = TextEncoder; }` in `setup()`. – Justin Harris Aug 29 '19 at 17:02
  • 3
    Since `typeof TextEncoder === 'undefined'` will always return `false`. Need to change it to: `typeof this.global.TextEncoder === 'undefined'` – EliSherer Dec 12 '19 at 19:59
  • 3
    I did this, and add `'testEnvironment': '/test/custom-test-env.js'` on `jest.config.js` – Cava Mar 11 '21 at 15:46
  • Nice solution, thank you. If you want to use this env only for 1 file or test you can add those lines to top of your unit test file: /** * @jest-environment ./tests/environments/custom-js-dom-env.ts */ – Serdar D. Sep 27 '21 at 13:46
  • @SerdarD. `'custom-js-dom-env.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module. ts(1208)`. And if I add an empty `export {}` I get: `Unexpected token 'export'` when the test runs. – Paul Nov 29 '21 at 21:32
  • Nevermind, solution for me was to use `window.document` instead of JSDom – Paul Nov 29 '21 at 22:11
  • 6
    Hmm, I tried this as described and got an error "TypeError: Class extends value # is not a constructor or null". I had to switch to `jest-environment-jsdom-global` – Ruben Martinez Jr. May 13 '22 at 18:58
19

I am getting same error for my Node.Js project. For testing purpose I used jest there. So following steps are resolved my issue

step-1: on the root folder of your project add a file named as jest.config.js

step-2: Add the following lines in the jest.config.file:

    module.exports = {
        testEnvironment: "node"
    };
  • 3
    Thanks. This worked for me and is the cleaner and shorter solution. – Yada Nov 01 '21 at 20:35
  • This deserves 100 up-votes!!! – dragonfly02 Aug 23 '22 at 06:57
  • 1
    For sure if you are using a nodeJS project this should be higher in the documentation – a53-416 Sep 16 '22 at 19:26
  • 1
    You could also just leave the default, which is node, and add a docblock at the beginning of your test file ```js /** * @jest-environment jsdom */ test('use jsdom in this test file', () => { const element = document.createElement('div'); expect(element).not.toBeNull(); }); ``` Ref: https://jestjs.io/docs/configuration#testenvironment-string – Chukwuma Nwaugha Dec 09 '22 at 15:24
16

Thanks for these answers. A simpler format that seems to work, at least with testEnvironment: 'jsdom' is:

  setupFiles: [`<rootDir>/jest-shim.js`],

jest-shim.js:

import { ArrayBuffer, TextDecoder, TextEncoder, Uint8Array } from 'util';

global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
global.ArrayBuffer = ArrayBuffer;
global.Uint8Array = Uint8Array;
Mosesoak
  • 181
  • 1
  • 7
  • This solved the `TextEncoder` error for me (jest 28 w/babel), but, on `import NextAuth from 'next-auth'` I get `TypeError: Uint8Array is not a constructor` :( – Devin Rhode Jun 03 '22 at 05:01
  • 1
    Ok, so if I just REMOVE `global.Uint8Array = Uint8Array;` then the error regarding `Uint8Array` disappears! – Devin Rhode Jun 03 '22 at 05:02
  • 1
    I like this solution because it is dead simple, visually it's very clean – Devin Rhode Jun 03 '22 at 05:03
4

I faced this problem when using mongodb. I used @Phoenix solution with a little change.

First I used jest-environment-node instead of jest-environment-jsdom:

const NodeEnvironment = require('jest-environment-node');

// A custom environment to set the TextEncoder that is required by mongodb.
module.exports = class CustomTestEnvironment extends NodeEnvironment {
  async setup() {
    await super.setup();
    if (typeof this.global.TextEncoder === 'undefined') {
      const { TextEncoder } = require('util');
      this.global.TextEncoder = TextEncoder;
    }
  }
}

Then I added the environment in the jest configs for all tests as Cava said in the comments:

// package.json
{
  ...
  "jest": {
    ...
    "testEnvironment": "<rootDir>/tests/custom-test-env.js"
  }
  ...
}
  • I had to add a decoder as well but this totally worked for me! Thank you! – Lakshya Oct 23 '21 at 16:23
  • 1
    [Here is an example](https://github.com/inrupt/solid-client-authn-js/blob/main/tests/environment/customEnvironment.js) of the custom environment configuration file that contains more assignements, and that worked for me. – Roman Mkrtchian Jan 01 '22 at 21:44
3

According to the latest jest v28 and react 18 I had to modify a bit the script

so my preSetup.js

const Environment = require('jest-environment-jsdom-global');
/**
 * A custom environment to set the TextEncoder
 */
module.exports = class CustomTestEnvironment extends Environment {
    constructor({ globalConfig, projectConfig }, context) {
        super({ globalConfig, projectConfig }, context);
        if (typeof this.global.TextEncoder === 'undefined') {
            const { TextEncoder } = require('util');
            this.global.TextEncoder = TextEncoder;
        }
    }
};
Jerryh001
  • 766
  • 2
  • 11
ayxos
  • 408
  • 1
  • 7
  • 15
3

None of the previously posted solutions worked in our case. The only thing that did help was adding the following lines to our jest.config.js file:

module.exports = {
      globals: {
        TextEncoder: require('util').TextEncoder,
        TextDecoder: require('util').TextDecoder,
      },
};
Rajoe
  • 111
  • 4
2

next babel works

  ● Test suite failed to run

    ReferenceError: TextEncoder is not defined

      1 | import assert from 'assert'
    > 2 | import { fromUrl, parseDomain, ParseResultType } from 'parse-domain'
        | ^
      3 | import { toUnicode } from 'punycode'
// ...
setupFiles: [`<rootDir>/jest-shim.js`],
testEnvironment: 'jest-environment-jsdom',
// ...
import { TextDecoder, TextEncoder } from 'util'

global.TextEncoder = TextEncoder
global.TextDecoder = TextDecoder
xiaotian
  • 59
  • 4
  • 4
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 25 '22 at 14:29
1

Add this to src/setupTests.ts

import '@testing-library/jest-dom';
import { TextEncoder } from 'util';

global.TextEncoder = TextEncoder;
Clark
  • 169
  • 3
  • 11
1
module.exports = {
      globals: {
        TextEncoder: require('util').TextEncoder,
        TextDecoder: require('util').TextDecoder,
      },
};

it worked for me! for newbie: remember to add lib:

yarn add util
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Alex Lee
  • 34
  • 2
0

Doesnt work using below config

"testEnvironment": "<rootDir>/tests/custom-test-env.js"

const NodeEnvironment = require('jest-environment-node');

// A custom environment to set the TextEncoder that is required by mongodb.
module.exports = class CustomTestEnvironment extends NodeEnvironment {
  async setup() {
    await super.setup();
    if (typeof this.global.TextEncoder === 'undefined') {
      const { TextEncoder } = require('util');
      this.global.TextEncoder = TextEncoder;
    }
  }
}

using "jest": "^28.0.3",

Brendon
  • 461
  • 1
  • 5
  • 10
0

Although all other answers seems to work fine, but I couldn't use any of them in my react-scripts app.

to fix the problem, I used this approach and it solved my issue.

This approach doesn't require adding a test environment as jest config.

farzad
  • 368
  • 1
  • 3
  • 15
0

An alternative approach is to wrap jest-environment-jsdom module with something that augment the env once and use that file instead in jest.config.js ... at least this solution worked well for us and it can be extended to include more missing globals.

Andrea Giammarchi
  • 3,038
  • 15
  • 25