import 'useAuthenticator' from aws-amplify outputs "TypeError: window.URL.createObjectURL is not a function" when jest testing - reactjs

I would like to know how to mock this kind of dependency's error. If I omit this import and comment out related source code, the test process is going to be fine. Already have got through Mock Function Jest, but no idea what I should do.
*This project was created by CRA with template typescript.
App component.
import { useAuthenticator } from '#aws-amplify/ui-react';
const App = () => {
const { user } = useAuthenticator((context) => [context.user]);
return (
<div>{user?.username}</div>
);
};
export default App;
Test code here.
import App from 'src/App';
import { render, screen } from '#testing-library/react';
describe('First group',()=>{
test('rendering App component',()=>{
// App includes 'import { useAuthenticator } from '#aws-amplify/ui-react';'
render(<App/>)
screen.debug()
})
})
Error outputs here.
● Test suite failed to run
TypeError: window.URL.createObjectURL is not a function
> 1 | import { useAuthenticator } from '#aws-amplify/ui-react';
| ^
2 |
3 | const App = () => {
4 | const { user } = useAuthenticator((context) => [context.user]);
at define (node_modules/maplibre-gl/dist/maplibre-gl.js:25:43)
at node_modules/maplibre-gl/dist/maplibre-gl.js:35:1
at node_modules/maplibre-gl/dist/maplibre-gl.js:3:81
at Object.<anonymous> (node_modules/maplibre-gl/dist/maplibre-gl.js:6:2)
at Object.<anonymous> (node_modules/#aws-amplify/ui-react/dist/index.js:1:484)
at Object.<anonymous> (src/App.tsx:1:1)
Based on slideshowp2's answer, added setup and config file. But still hits the error. Both are located rootDir.
jest.setup.js
if (typeof window.URL.createObjectURL === 'undefined') {
window.URL.createObjectURL = jest.fn();
}
jest.config.js
module.exports = {
setupFilesAfterEnv: ['./jest.setup.js'],
};
UPDATE
This PJ was created by CRA so it required to follow their rule. I finally resolved this by slideshowp2's answer and the correct location to put the pollyfilling code.
src/setupTests.ts
if (typeof window.URL.createObjectURL === 'undefined') {
window.URL.createObjectURL = jest.fn();
}

See troubleshooting#jest
As of v2.15.0 of #aws-amplify/ui-react which included the release of Geo components, users of the Jest testing framework may run into the following error when attempting to run tests:
window.URL.createObjectURL is not a function
Please follow the steps below to resolve this issue.
Navigate to or create a Jest setup file for your project.
Add the following code to polyfill the unrecognized function in your Jest setup file:
jest.setup.js:
if (typeof window.URL.createObjectURL === 'undefined') {
window.URL.createObjectURL = jest.fn();
}
jest.config.js:
module.exports = {
//...
setupFilesAfterEnv: ['./jest.setup.js'],
//...
}
This is a known problem when using the jsdom library (a dependency of Jest) with a package that uses an unrecognized function. See this issue.

Related

expect(...).toHaveAttribute is not a function - Why?

I created some basic tests and followed the getting started guide on Jests website, but toHaveAttribute is not a function apparently
import React from "react";
import { fireEvent, render } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import { App } from "../App";
test("allows users to add items to their list", () => {
const { getByText, getByLabelText, getByTestId } = render(<App />);
const input = getByLabelText("What needs to be done?");
userEvent.type(getByTestId("email"), "Hello World!")
expect(getByTestId("email")).toHaveAttribute("value", "Hello, World!")
})
TypeError: expect(...).toHaveAttribute is not a function
10 | const input = getByLabelText("What needs to be done?");
11 | userEvent.type(getByTestId("email"), "Hello World!")
> 12 | expect(getByTestId("email")).toHaveAttribute("value", "Hello, World!")
| ^
13 | })
I followed the tutorial exactly so im unsure why this is happening.
The method toHaveAttribute is part of jest-dom that enables to test DOM elements. You need to verify if you have setup it properly at your project.
Install the module:
npm install --save-dev #testing-library/jest-dom
After that you can include at your jest setup file like recommended:
// In your own jest-setup.js (or any other name)
import '#testing-library/jest-dom'
// In jest.config.js add (if you haven't already)
setupFilesAfterEnv: ['<rootDir>/jest-setup.js']
I faced the same issue but easily solved it using playwright native getter - .getAttribute('someAttribute');
For example you can write something like that:
const locator = await page.locator('[name="SomeLocator"]').getAttribute('content');
I have an alternative where you use getAttribute
import { render, screen } from '#testing-library/svelte';
it('should render avatar of user', async () => {
const image = screen.getByAltText(`${MOCK_NAVBAR.username} avatar`);
expect(image.getAttribute('src')).toBe(MOCK_NAVBAR.userAvatar);
});

How to test a potential null value from an async void function without using #ts-ignore in TypeScript

This is probably more of a JavaScript/TypeScript question then it is about React/Testing.
But I'll give the complete story. So I have a test app with basic routing functionality and tests to verify that the routing works.
App.tsx
https://github.com/Leejjon/pwa-seo/blob/6f621968de1184b03744a262a68d291b4571c5c1/src/App.tsx
App.test.tsx
https://github.com/Leejjon/pwa-seo/blob/6f621968de1184b03744a262a68d291b4571c5c1/src/App.test.tsx
Everything worked fine. Then I added an useEffect hook to initialize my internationalization library:
useEffect(() => {
async function initMessages() {
await intl.init({
currentLocale: "en-US",
locales
});
}
initMessages().then(() => setLoading(false));
}, [loading]);
This loads all my text assets in English. This works fine, but broke all my tests with the following error message:
Warning: An update to App inside a test was not wrapped in act(...).
After some reading up on the internet I managed to fix my tests by adding this 'act' function, here is one example:
import React from 'react';
import {act, render, fireEvent, waitForElement} from '#testing-library/react';
import "#testing-library/jest-dom/extend-expect";
import App from './App';
test('Verify home page header', async() => {
let app: HTMLElement;
await act(async () => {
const {container} = render(<App/>);
app = container;
});
// #ts-ignore
if (app) {
const pageHeaderContent = app.querySelector("#pageHeader")?.firstChild?.textContent;
expect(pageHeaderContent).toMatch('Home page');
} else {
fail("The app should have been initialized.");
}
});
Right now I'm suppressing the TS2454: Variable 'app' is used before being assigned. warning with the #ts-ignore. This is ugly. If I move my assertions into the act function, I get the same Warning: An update to App inside a test was not wrapped in act(...). error again.
Is there a way to obtain the container object destructured from the render function without having to use the #ts-ignore and the if clause to do null checking?
I created a tag for the current code related to this question:
https://github.com/Leejjon/pwa-seo/releases/tag/uglylines
Link to last commit: https://github.com/Leejjon/pwa-seo/commit/2434f78c0619be2d55f9de965149f6bd6d1a0b90
Typescript is complaining about the app variable to not have been initialised when you access it in the if-statement. You can simply fix that by assigning null to it.
let app: HTMLElement = null;
In case you use strict null checks you have to allow null on the type:
let app: HTMLElement | null = null;
After puzzling this is my result
test('Verify home page header', async() => {
let app: HTMLElement | undefined = undefined;
await act(async () => {
const {container} = render(<App/>);
app = container;
});
let appAsHtmlElement = (app as unknown as HTMLElement);
const pageHeaderContent = appAsHtmlElement.querySelector("#pageHeader")?.firstChild?.textContent;
expect(pageHeaderContent).toMatch('Home page');
});
Better suggestions (if there is some way of not having to use the 'act' function) are still welcome.

How to fix 'window.URL.createObjectURL is not a function' when testing mapbox-gl in React?

I'm testing React component with Mapbox, material-ui and custom styles. I use Jest + Enzyme for testing.
I have problem: 'window.URL.createObjectURL is not a function'. I read similar questions:
github.com/uber/react-map-gl/issues/210
github.com/mapbox/mapbox-gl-js/issues/3436
github.com/mapbox/mapbox-gl-js-mock
and tried to add something but without success. Please, fix the issue.
CodeSandbox
I had faced exactly same issue with my jest test suite. After some trial and searching, I was able to mock the createObjectURL method.
In jest.stub.js file, I put this config:
if (typeof window.URL.createObjectURL === 'undefined') {
window.URL.createObjectURL = () => {
// Do nothing
// Mock this function for mapbox-gl to work
};
}
Then, in jest.config.js file, I added a reference to the stub file
setupFiles: [
'<rootDir>/tests/jest.stub.js',
],
Note: make sure you get the path right in setupFile defintion.
I had the same issue running tests using the library Plotly.js with React and Jest.
My solution was to add a file src/setupTests.js with a mock for the createObjectURL function window.URL.createObjectURL = function() {};
I also was using React with Mapbox-gl and #Pablo Jurado's solution worked perfectly.
Just pasted window.URL.createObjectURL = function() {};
to src/setupTest.js file
and also modified npm test script to:
"scripts": { "test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!your-module-name)/\"", },
based on
this example
Add package: mapbox-gl-js-mock
add require("mapbox-gl-js-mock"); before jest.mock(
import React from 'react';
import { createShallow } from '#material-ui/core/test-utils';
import App from './App';
require("mapbox-gl-js-mock");
jest.mock('mapbox-gl/dist/mapbox-gl', () => ({
App: () => ({}),
}));
describe('<App />', () => {
let shallow;
beforeEach(() => {
shallow = createShallow({ dive: true });
});
it('renders without crashing', () => {
const wrapper = shallow(<App />);
expect(wrapper.find('.MapBox')).toExist();
});
});

`ReferenceError: TextEncoder is not defined` when running `react-scripts test --env=jsdom`

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.
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
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"
};
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;
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"
}
...
}
According to the latest jest v28 (https://jestjs.io/docs/upgrading-to-jest28) 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;
}
}
};
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
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",
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.
Add this to src/setupTests.ts
import '#testing-library/jest-dom';
import { TextEncoder } from 'util';
global.TextEncoder = TextEncoder;

Enzyme Jest window.getSelection() does not work

How to fix my situation? Jest + Enzyme testing of function below returns
TypeError: window.getSelection is not a function
My code:
_addNewAgent () {
window.getSelection().removeAllRanges();
const newAgent = generateNewAgent();
const newDataBase = this.state.agentsDatabase;
newDataBase.push(newAgent);
this.setState({
currentAgentProfile: newAgent,
agentsDatabase: newDataBase,
infoDisplayContent: 'profile'
});
}
My test:
import React from 'react';
import { mount } from 'enzyme';
import App from '../containers/App';
const result = mount(
<App />
);
test('add agent', () => {
const agentsList = result.state().agentsDatabase.length;
result.find('#addNewAgent').simulate('click');
expect(result.state().agentsDatabase.length).toBe(agentsList + 1);
expect(result.state().currentAgentProfile)
.toEqual(result.state().agentsDatabase[agentsList]);
expect(result.state().infoDisplayContent).toBe('profile');
});
You have to stub out window.getSelection().removeAllRanges(). I think this would work:
before(() => {
window.getSelection = () => {
return {
removeAllRanges: () => {}
};
})
});
2020 Update
I was struggling with this as well and #Mohammad comment pointed me in the right direction, so I decided to add that as an answer here to help others:
As mentioned by #Mohammad, jsdom is now on version 16, which supports getSelection. However, Jest#25 is still using older versions of jsdom, so what you can do is use this NPM package to "by pass" that:
https://www.npmjs.com/package/jest-environment-jsdom-sixteen
After the install, just update your Jest config to use this:
{
"testEnvironment": "jest-environment-jsdom-sixteen"
}
Or, use it as a flag direct in the NPM command:
npm run test --env=jsdom-sixteen
for me, I also need this mock window.document.getSelection = jest.fn()

Resources