Тестирование реакции: целевой контейнер не является элементом DOM

#javascript #reactjs #ecmascript-6 #tdd

#javascript #reactjs #ecmascript-6 #tdd

Вопрос:

Я пытаюсь протестировать компонент React с помощью Jest / Enzyme при использовании Webpack.

У меня очень простой тест @

 import React from 'react';
import { shallow } from 'enzyme';

import App from './App';

it('App', () => {
  const app = shallow(<App />);
  expect(1).toEqual(1);
});
  

Относительный компонент, который он собирает, является :

 import React, { Component } from 'react';
import { render } from 'react-dom';

// import './styles/normalize.css';

class App extends Component {
  render() {
    return (
      <div>app</div>
    );
  }
}

render(<App />, document.getElementById('app'));
  

Однако запуск jest приводит к сбою:

Invariant Violation: _registerComponent(...): Target container is not a DOM element.

С ошибками @

at Object.<anonymous> (src/App.js:14:48)
at Object.<anonymous> (src/App.test.js:4:38)

Тестовые файлы ссылаются на строку 4, которая является импортом <App /> , что вызывает сбой. Трассировка стека указывает, что причиной сбоя является строка 14 из App.js , что является не чем иным, как вызовом рендеринга react-dom , с чем у меня никогда не было проблем (приложение правильно отображается из моей настройки Webpack).

Для тех, кто заинтересован (код Webpack):

 module.exports = {
  entry: './src/App',
  output: {
    filename: 'bundle.js',
    path: './dist'
  },
  module: {
    loaders: [
      {
        test: /.js?$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: {
          presets: ['react', 'es2015']
        }
      },
      {
        test: /.css$/,
        loader: 'style!css-loader?modulesamp;importLoaders=1amp;localIdentName=[name]__[local]___[hash:base64:5]'
      },
      {
        test: /.scss$/,
        loader: 'style!css-loader?modulesamp;importLoaders=1amp;localIdentName=[name]__[local]___[hash:base64:5]!sass'
      }
    ]
  }
}
  

И мой package.json :

 {
  "name": "tic-tac-dux",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --devtool eval --progress --colors --inline --hot --content-base dist/",
    "test": "jest"
  },
  "jest": {
    "moduleNameMapper": {
      "^. \.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
      "^. \.(css|sass)$": "<rootDir>/__mocks__/styleMock.js"
    }
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.17.0",
    "babel-jest": "^16.0.0",
    "babel-loader": "^6.2.5",
    "babel-polyfill": "^6.16.0",
    "babel-preset-es2015": "^6.16.0",
    "babel-preset-react": "^6.16.0",
    "css-loader": "^0.25.0",
    "enzyme": "^2.4.1",
    "jest": "^16.0.1",
    "jest-cli": "^16.0.1",
    "node-sass": "^3.10.1",
    "react-addons-test-utils": "^15.3.2",
    "react-dom": "^15.3.2",
    "sass-loader": "^4.0.2",
    "style-loader": "^0.13.1",
    "webpack": "^1.13.2",
    "webpack-dev-server": "^1.16.2"
  },
  "dependencies": {
    "react": "^15.3.2",
    "react-dom": "^15.3.2"
  }
}
  

О, и если кто-нибудь собирается сказать, что элемент div не загружается перед скриптом, вот мой index.html :

 <!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>App</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="/bundle.js"></script>
  </body>
</html>
  

В чем может быть причина этой специфической проблемы с рендерингом? Что-то связано с новым обновлением Jest до 15.0?

Ответ №1:

Для всех, кто прочесывал Интернет, как я, — искал решение этой проблемы при тестировании с помощью Jest — я подкреплю ответ @biphobe, сказав, что Jest приведет к возникновению этой ошибки при экспорте чего-либо внутри того же файла, который вызывает ReactDOM.render.

В моем случае я экспортировал объект в свой index.js где я также вызывал ReactDOM.render. Я удалил этот экспорт и вуаля!

Комментарии:

1. Спасибо, это было именно то, что я искал. Явное упоминание о вызове ReactDOM.render() было именно тем, что мне нужно было, чтобы понять точную проблему!

2. Рад, что это было полезно!

3. у меня возникла аналогичная проблема при попытке импортировать действия в моем тесте. К сожалению, удаление ReactDOM не помогает

4. @PraveenSrinivasan У меня возникли проблемы с Jest, когда я попытался вызвать ReactDOM.render и экспортировать что-то в одном файле javascript.

5. Ах да! Спасибо. Я добавил экспорт объекта в свой входной файл, который использует ReactDom.render . Но для этого файла пока нет тестов.

Ответ №2:

App.jsx предполагается, что он экспортирует класс приложения и больше ничего не делает, render должен вызываться в другом месте.

Если вы удалите render вызов из App.ошибка jsx должна исчезнуть, она появляется, потому что тестовая среда не предоставляет DOM app идентификатор.

Комментарии:

1. я получил ту же ошибку: — я протестирован с ava .. пожалуйста, скажите мне, где я должен ошибаться .. экспорт класса по умолчанию App расширяет компонент { render() { return ( <div> {this.props.children} </div> ) } } render((> <Путь маршрута = «/» компонент = {приложение}> <Компонент IndexRoute={Индекс} /> <Путь маршрута = «страница 1» компонент = {Страница 1} /> <Путь маршрута = «страница 2» компонент = {Страница 2} /> </Маршрут> </Маршрутизатор> ), document.getElementById(‘app’));

2. @BhavikKheni Пожалуйста, создайте новый вопрос и включите в него свой полный код вместе с ошибкой и трассировкой стека.

3. Где его следует вызывать, если нет, если не в App.jsx?

Ответ №3:

Как я вижу, эта ошибка возникает во многих случаях и требует разных подходов для ее решения. Мой сценарий отличается от приведенного выше примера, я использую redux amp; router, хотя я боролся с той же ошибкой. Что помогло мне решить эту проблему, так это перейти index.js от:

 ReactDOM.render(
  <Provider store={store}>
    <AppRouter />
  </Provider>,
  document.getElementById("root")
);
registerServiceWorker();
  

Для:

 ReactDOM.render(
    (<Provider store={store}>
        <AppRouter/>
    </Provider>),
     document.getElementById('root') || document.createElement('div') // for testing purposes
);
registerServiceWorker();
  

Комментарии:

1. очень полезный ответ — расстраивает, потому что я не могу понять, поддерживается ли это; Интересно, это потому, что мы используем Provider?

2. переход с «двойных кавычек» на «одинарные кавычки» сработал для меня, спасибо!!

3. @bibangamba я удивлен, что это имеет значение. Можете ли вы уточнить?

Ответ №4:

Я нашел решение для этой ошибки в моем случае использования: использование того же хранилища Redux, которое React использует за пределами React.

При попытке экспортировать Redux моего React store из index.tsx для использования где-то еще за пределами приложения React я получал ту же ошибку при выполнении тестов Jest (которые используют Enzyme) в App.tsx файле.

Ошибка

Исходный код, который не работал при тестировании React, выглядел следующим образом.

 // index.tsx

import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import { applyMiddleware, compose, createStore } from "redux";
import App from "./components/App";
import { rootReducer } from "./store/reducers";
import { initialState } from "./store/state";

const middlewares = [];

export const store = createStore(
    rootReducer,
    initialState,
    compose(applyMiddleware(...middlewares)),
);

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById("root"),
);
  

Решение, которое сработало для меня

Разделите логику хранилища Redux в новый файл с именем store.ts , затем создайте a default export (для использования index.tsx , т.Е. Приложением React) и экспорт по умолчанию с export const store помощью (для использования из классов, отличных от React) следующим образом.

 // store.ts

import { applyMiddleware, compose, createStore } from "redux";
import logger from "redux-logger";
import { rootReducer } from "./store/reducers";
import { initialState } from "./store/state";

const middlewares = [];

export const store = createStore(
    rootReducer,
    initialState,
    compose(applyMiddleware(...middlewares)),
);

export default store;
  
 // updated index.tsx

import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import App from "./components/App";
import store from "./store";

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById("root"),
);
  

Использование хранилища Redux в классах, отличных от React

 // MyClass.ts

import { store } from "./store"; // store.ts

export default class MyClass {
  handleClick() {
    store.dispatch({ ...new SomeAction() });
  }
}
  

Экспорт default

Небольшое замечание, прежде чем вы уйдете. Вот как использовать default экспорт и экспорт по умолчанию.

  • default export store; используется с import store from "./store";
  • export const store = ... используется с import { store } from "./store";

Надеюсь, это поможет!

https://nono.ma/says/solved-invariant-violation-target-container-is-not-a-dom-element

Комментарии:

1. Не приведет ли это к созданию двух хранилищ?

2. Разве мы не можем использовать import store from "./store"; в наших файлах без реакции? Таким образом, избегая необходимости экспорта по умолчанию в store.ts

3. Спасибо за ваш вопрос @IvOv. Я написал это год назад, и, похоже, в этом весь смысл. Мне пришлось бы протестировать его снова, но, насколько я помню, доступ к тому же хранилищу, к которому обращался React, не работал для меня, и именно поэтому мне пришлось использовать этот обходной путь.

4. Это решило проблему для меня!! Большое вам спасибо за это решение 🙂 вопрос об этом решении, если мы импортируем «хранилища» как в «MyClass.ts», так и в «index.ts», разве это не означает, что мы дважды вызывали createStore()? Спасибо! @Nono

5. Это было для меня. Потребовался небольшой рефакторинг, чтобы удалить store создание из index.tsx, но как только я переместил его в его собственный файл и импортировал оттуда, тест прошел. Почему jest не похож на хранилище, экспортируемое из index.tsx / js?

Ответ №5:

Убедитесь, что в вашем тестовом файле вы правильно импортировали компонент рендеринга. Он должен быть импортирован @testing-library/react не из react-dom :

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

Ответ №6:

Ну, мы не можем запретить разработчикам экспортировать компонент из любого файла и тестировать его изолированно, даже если в нем есть импорт или использование react-dom.Я имею в виду, что в этом не так. Мы не пытаемся нарушить весь файл и протестировать некоторые его фрагменты, если это допустимый фрагмент кода .

У Jest нет проблем с react-dom , однако концептуально они разные . Предположительно, Jest — это виртуальная тестовая среда без браузера . React-DOM — это библиотека, которая выполняет сшивание виртуального DOM с реальным DOM для компонентов react . Настолько очевидно, что мы можем / не должны тестировать его обычным способом. Но это пока не обсуждается. мы в порядке, пока наши экспортируемые компоненты можно тестировать .

Итак, давайте издеваться над ним

Я сделал макет в файле TestSetup, настроенном с помощью «setupFilesAfterEnv» в конфигурации jest.

 jest.mock("react-dom", () => {
    return ({
        "render": jest.fn()
    })
})
  

Это в значительной степени сработало для меня. Мой код react и react-dom теперь удачно сочетаются в одном файле, работает как в браузере, так и в среде тестирования .

Я не сталкивался с какими-либо проблемами из-за этого. Если есть какие-либо, я буду смотреть в раздел комментариев

Ответ №7:

Это решение сработало для меня. Просто визуализируйте, если элемент есть:

 const root = document.getElementById('root');
if (root) {
    render(
        <App />,
        root,
    );
}
  

Ответ №8:

Я обнаружил, что эта ошибка также может возникать при работе с Portals вашими тестами. Если вы хотите пропустить ошибку, вы можете mock Portals либо добавить элемент Portal контейнера в свой метод рендеринга:

 render (
  <div>
    <TestedComponent />
    <div id="portal" />
  </div>
)