dom-testing-library: query* causes TypeError: Converting circular structure to JSON

EDIT: Updated to reflect my latest findings

  • @testing-library/dom version: 7.29.4
  • Testing Framework and version: Jest 26.6.3
  • DOM Environment: jsdom 16.4.0
  • React version: happens on both 16.13.1 and 17.0.1
  • Node version: v12.18.3

Relevant code or config:

import React from 'react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';

describe('test test', () => {
  it('example test', () => {
    const { queryAllByText } = render(
      <div>
        <div>test thing</div>
        <div>test thing</div>
      </div>
    );
    expect(queryAllByText('test thing')).toEqual(123);
  });
});

What you did:

I was trying to query deep element for clicking it. The page contains two matching components with the same text

What happened:

I’m getting following:

(node:38467) UnhandledPromiseRejectionWarning: TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'HTMLDivElement'
    |     property '__reactInternalInstance$e23phu313qm' -> object with constructor 'FiberNode'
    --- property 'stateNode' closes the circle
    at stringify (<anonymous>)
    at writeChannelMessage (internal/child_process/serialization.js:117:20)
    at process.target._send (internal/child_process.js:779:17)
    at process.target.send (internal/child_process.js:677:19)
    at reportSuccess (/Users/path/to/project/node_modules/jest-runner/node_modules/jest-worker/build/workers/processChild.js:67:11)

Reproduction:

  1. run the above example with jest --watch (babel and jest configs can be very basic or even default)
  2. either restart Jest, or change the file to trigger second run
  3. You should get the error above

The error happens when Jest runs the snippet for second time or more. It can be resolved by wiping the query cache using jest --clearCache, but it reappears on second test run.

Problem description:

The test crashes and leaves Jest hanging. This does not happen if I’m not querying the component with anything, but does happen when querying, at least with queryByText and queryAllByText.

Suggested solution:

The above example should not crash. I hope the stacktrace and the description gives enough clue on what might be the culprit here.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 8
  • Comments: 26 (8 by maintainers)

Most upvoted comments

The error seems to happen when HTML elements are passed to expect as a value to be compared. When an assertion fails jest tries to format the test output and somewhere along the way it passes the argument of expect to JSON.stringify. The elements contain circular references so stringifying throws.

Here’s a crude monkeypatch to delete the circular reference when making assertions. It doesn’t handle all cases and might have side-effects but it was good enough for my use case and hopefully highlights where the issue lies.

In global setup file (“setupFilesAfterEnv”):

const removeReactInternalInstance = element => {
  const keys = Object.keys(element);
  const reactInternalInstanceKey = keys.find(key => /^__reactInternalInstance/.test(key));
  if (reactInternalInstanceKey != null) delete element[reactInternalInstanceKey];
};
const { expect } = window;
window.expect = (actual, ...rest) => {
  if (typeof actual === 'object' && actual !== null) {
    if (Array.isArray(actual)) {
      actual.forEach(removeReactInternalInstance);
    } else {
      removeReactInternalInstance(actual);
    }
  }
  return expect(actual, ...rest);
};
Object.entries(expect).forEach(([key, value]) => window.expect[key] = value);

I would argue this is a bug in Jest rather than a bug in testing-library. Jest should be able to format objects with circular references or at least give an informative error message.

@timdeschryver pushed up here: https://github.com/agmcleod/testinglib-cyclic-error, sadly though it seems to be intermittent for me. I can re-run it multiple times in a row and it will be fine. But occasionally I will see:

    TypeError: Converting circular structure to JSON
        --> starting at object with constructor 'HTMLParagraphElement'
        |     property '__reactFiber$2jyvco3rr65' -> object with constructor 'FiberNode'
        --- property 'stateNode' closes the circle
        at stringify (<anonymous>)

Isn’t that because query is actually a Promise? image

doing so will fail

image

but this works

image

Maybe this would help someone:

// if expect do not exist
await expect(screen.findByTestId(contextMenuId)).rejects.toThrowError();
// expect it exist
await expect(screen.findByTestId(contextMenuId)).resolves.toBeDefined();

I’m closing this one as this is a jest issue and will do my best to assist in fixing the jest issue as I understand that this might happen in every react app on a failing test.

Digging into it, this looks heavily related to this issue in jest.

Was able to reproduce generating an app with create-react-app and then adding this test to App.test.js:

function Text(props) {
  return <p>{props.children}</p>
}

test('renders a paragraph', () => {
  render(<Text>hi</Text>)
  expect(screen.getAllByText('hi')).toEqual([])
})

Versions in package.json:

{
  "name": "testlib-cyclic",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^12.0.0",
    "@testing-library/user-event": "^13.2.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "5.0.0",
    "web-vitals": "^2.1.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

@MatanBobi

I had the circular error issue with this code

expect(screen.queryByRole('button', {name: 'Add'})).toBe(null)

For now, resorted to the following workaround:

expect(screen.queryByRole('button', {name: 'Add'}) === null).toBe(true)