Redux: Тестирование связанных компонентов, которые имеют вложенные связанные компоненты внутри

Созданный на 10 нояб. 2016  ·  21Комментарии  ·  Источник: reduxjs/redux

Привет, это скорее вопрос, чем проблема.

Недавно я начал использовать React и Redux для проекта, и у меня возникла пара проблем с тестированием, но документация, похоже, не дает окончательного решения проблемы.

В настоящее время у меня есть подключенный компонент, для которого я хотел бы написать несколько модульных тестов, однако есть подключенный компонент, вложенный в первый компонент, и я, похоже, в конечном итоге блокирую свойства или состояние (или в большинстве случаев оба) из вложенный компонент не устанавливается.

Мне было интересно, есть ли способ заглушить один компонент, чтобы я мог сосредоточиться только на компоненте, который пытаюсь протестировать?

заранее спасибо

question

Самый полезный комментарий

Здесь возникает проблема, когда тестируемый компонент затем отображает другие подключенные компоненты либо как прямые дочерние элементы, либо где-то ниже по иерархии. Если вы выполняете полный рендеринг через mount() , тогда подключенные компоненты-потомки будут пытаться получить доступ к this.context.store и не найдут его. Итак, основные варианты - либо убедиться, что потомки не отображаются, выполнив неглубокий тест, либо фактически сделать хранилище доступным в контексте.

Все 21 Комментарий

Две вещи:

1) это использование и, вероятно, должно перейти к переполнению стека.

2) часть удобства использования компонента connect более высокого порядка
заключается в том, что вы можете просто написать модульный тест для компонента unconnected и
доверяйте redux, чтобы обрабатывать трубопроводы в магазине, как следует

Если неясно, что я имею в виду, могу опубликовать пример.

Пара мыслей:

  • Один из способов сосредоточиться только на одном компоненте - использовать «неглубокий» рендеринг, который фактически не отображает никаких дочерних элементов.
  • Вы также можете отображать <Provider store={testStore}><ConnectedComponent /></Provider> в своих тестах

К вашему сведению, у меня есть ссылки на ряд статей о тестировании React / Redux, которые могут быть вам полезны: https://github.com/markerikson/react-redux-links/blob/master/react-redux-testing. мкр .

И да, как вопрос использования, его действительно лучше задать в другом месте.

Спасибо @markerikson В настоящее время я пробую последний метод, о котором вы упомянули, но также

Я все еще думаю, что если вы хотите протестировать этот когда-то компонент изолированно, вам лучше сделать что-то вроде

// Component.js
import React from 'react'

export const Component = ({ a, b, c }) => ( 
  // ...
)

// ...

export default connect(mapStateToProps, mapDispatchToProps)(Component)

тогда вы можете просто ввести компонент до его подключения в вашем тесте, например

// Component.spec.js
import { Component } from './Component.js'
import { mount } from 'enzyme'

it('should mount without exploding', () => {
  mount(<Component a={1} b={2} c={3} />)
})

Если вы действительно хотите проверить, что изменения в вашем магазине распространяются правильно, то имеет смысл визуализировать Provider и это другое, хотя я не уверен, что вы получите большую пользу от этого тестирования.

Здесь возникает проблема, когда тестируемый компонент затем отображает другие подключенные компоненты либо как прямые дочерние элементы, либо где-то ниже по иерархии. Если вы выполняете полный рендеринг через mount() , тогда подключенные компоненты-потомки будут пытаться получить доступ к this.context.store и не найдут его. Итак, основные варианты - либо убедиться, что потомки не отображаются, выполнив неглубокий тест, либо фактически сделать хранилище доступным в контексте.

@markerikson А, хорошо, я просто перечитал, я думал, что OP пытается проверить внутренний компонент, а не внешний. 👍

Спасибо за вашу помощь, ребята,

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

Насмешливое состояние в целом - это то, с чем я на самом деле немного боролся и не смог найти ничего убедительного по этому поводу, поэтому было бы здорово, если бы кто-нибудь мог дать мне некоторое представление о том, как я бы передал имитационное состояние в вложенный компонент или компонент более высокого порядка?

TIA

@StlthyLee : что вы имеете в виду, <Provider store={store}><ComponentWithConnectedDescendants /></Provider> в своем тесте, это должно сработать.

В этом проблема, просто это не обрабатывает его правильно, компонент, вложенный в, требует определенного параметра из состояния приложения, который в настоящее время он не получает по той или иной причине.

@StlthyLee, вероятно, следует добавить пример / gist / codepen / repo-link или что-то в этом роде, на выявление проблемы уйдет меньше времени, поскольку здесь, похоже, есть противоречия в терминологии.

@Koleok, надеюсь, это поможет прояснить

https://gist.github.com/StlthyLee/02e116d945867a77c382fa0105af1bf3

Если нет, пожалуйста, спросите, так как я очень хочу разобраться в этом, потратив последние 9-10 лет, работая в мире Rails и хорошо разбираясь в TDD с этой точки зрения, я сейчас пытаюсь понять моя голова вокруг TDD в мире React / Redux.

Итак, я считаю, что теперь я разобрался с этой проблемой, это была не столько проблема тестирования, сколько копия и прошлая ошибка, сделанная другим членом команды, я думаю, это неотъемлемая часть необходимости писать тесты на соответствие коду, а не мои любимые способы TDD. Спасибо за всю информацию, представленную здесь, это было чрезвычайно ценно

Чтобы предложить еще один вариант, который может работать для некоторых случаев использования, я недавно столкнулся с этой проблемой с вложенным подключенным компонентом. Вместо того, чтобы создавать имитацию хранилища, я экспортировал неподключенные версии каждого компонента для их модульного тестирования без хранилища. Что-то вроде этого:

class TopLevelImpl extends React.PureComponent {
  // Implementation goes here
}

// Export here for unit testing.  The unit test imports privates and uses TopLevel
// with Enzyme's mount().
export const privates = {
  TopLevel: TopLevelImpl,
}

export const TopLevel = connect(mapStateToProps)(TopLevelImpl)

Я сделал то же самое для дочерних вложенных компонентов, поэтому каждый из них можно тестировать независимо.

Затем я взял подключенный компонент верхнего уровня и передал дочерние подключенные компоненты в качестве свойств в функцию приложения render() . Однако при модульном тестировании компонента верхнего уровня вместо передачи связанных компонентов я просто передаю имитационные компоненты, чтобы гарантировать, что они отображаются в нужном месте. Вы также можете передать несвязанные компоненты.

Что-то вроде этого:

// Root render function
render() {
  return (
    <Provider store={store}>
      <TopLevel child1={<Child1 />} child2={<Child2 />} />
    </Provider>
  )
}

Мне действительно нравится твоя идея. Но вместо этого я делаю что-то вроде этого

const props = {
  child1: () => (<div />),
  child2: () => (<div />)
}
<TopLevel {...props} />

Я думаю, что если вы уже тестировали child1 и child2, то, вероятно, нет необходимости иметь их в вашем компоненте TopLevel.

Я также передаю функцию, если мне нужно условно отрендерить компонент.

Но я не следую твоему последнему предложению. Какое отношение имеет тестирование к передаче дочерних компонентов другому компоненту?

Хорошо. Например, у меня есть внешний подключенный компонент, который использует один или несколько внутренних подключенных компонентов . В этом случае, если я хочу протестировать внешний компонент, я делаю что-то вроде этого

wrapper = mount(<ExternalComponent.WrappedComponent {...props} />)

Однако у меня есть ошибка, что моим внутренним компонентам необходимо состояние для правильной работы.

Invariant Violation: Could not find "store" in either the context or props

Затем я попытался пройти тестовое состояние и т.д. Это сработало. Однако в моем случае мне нужно много инициализировать для каждого подключенного компонента, включая асинхронные вызовы, initialState и т.д., и у меня были другие ошибки, связанные с этим.

Я думаю, что это будет излишним, если я буду правильно инициализировать все внутренние подключенные компоненты, если я просто хочу протестировать внешний компонент.

Поэтому я решил переместить все подключенные компоненты и передать их в качестве свойств внешнему подключенному компоненту. И теперь я могу заменить их пустыми div, когда хочу протестировать.

Ах да, это была изначальная мотивация для паттерна. Итак, чтобы изменить ваше предложение, вы имели в виду: «Я думаю, что если вы уже протестировали child1 и child2, то нет необходимости передавать их в свой компонент TopLevel при его модульном тестировании».

Согласовано.

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

Решение, которое я нашел особенно полезным, - это подключить вложенный компонент к хранилищу redux в зависимости от тестовой среды.

  • При запуске тестов установите переменную среды NODE_ENV="test" . Вы можете поймать эту переменную и заставить компонент получать свойства по умолчанию. В этом случае компонент не подключен к хранилищу, и при тестировании родительского компонента никаких проблем не возникает.
  • В остальных случаях подключите компонент. Это сценарий по умолчанию при запуске компонента в приложении.

а) Во-первых, вам понадобится вспомогательная функция, которая определяет, является ли среда test :

export default function ifTestEnv(forTestEnv, forNonTestEnv) {
  const isTestEnv = process && process.env && process.env.NODE_ENV === 'test';
  const hoc = isTestEnv ? forTestEnv : forNonTestEnv;
  return function(component) {
    return hoc(component);
  };
}

б) Далее, defaultProps() и connect() должны применяться условно в зависимости от среды:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import defaultProps from 'recompose/defaultProps';

import ifTestEnv from 'helpers/if_test_env';
import { fetch } from './actions';

class MyComponent extends Component {
   render() {
      const { propA, propB } = this.props;
      return <div>{propA} {probB}</div>
   }

   componentDidMount() {
      this.props.fetch();
   }
}

function mapStateToProps(state) {
  return {
    propA: state.valueA,
    propB: state.valueB
  };
}

export default ifTestEnv(
  defaultProps({
    propA: 'defaultA',
    propB: 'defaultB',
    fetch() {   }
  }),
  connect(mapStateToProps, { fetch })
)(MyComponent);

c) Убедитесь, что NODE_ENV равно "test" при запуске тестов:

  • При использовании интерфейса командной строки mocha поставьте префикс для установки тестовой среды: NODE_ENV='test' mocha app/**/*.test.jsx .
  • В случае webpack примените плагин:
plugins: [
    // plugins...
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('test')
      },
    })
]

@markerikson Спасибо за два предложения. Я попробовал второй и обернул свой компонент для тестирования с помощью провайдера. В этом случае mapStateToProps работает правильно, но я не получаю ожидаемых результатов с mapDispatchToProps . Здесь тестируемый компонент отображает другие подключенные компоненты.

Моя mapDispatchToProps выглядит примерно так

/* <strong i="10">@flow</strong> */
import { bindActionCreators } from 'redux';

import type { Dispatch } from './types';
import * as actions from './actions';

export default (dispatch: Dispatch, ownProps: Object): Object => ({
  actions: bindActionCreators(actions, dispatch),
});

здесь возвращается значение действия undefined .

store.js

import { applyMiddleware, compose, createStore } from 'redux';
import { autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';

import rootReducer from './reducers';
import { REHYDRATE } from 'redux-persist/constants';

const store = compose(autoRehydrate(), applyMiddleware(thunk, createActionBuffer(REHYDRATE)))(createStore)(rootReducer);

export default store;

Я понимаю, что это довольно старый поток, но я обнаружил, что это немного неприятная проблема, и подумал, что это решение может помочь кому-то, кто наткнется на эту тему (например, я).

Поскольку я на самом деле не тестировал что-либо, связанное с Redux, я нашел простое решение - имитировать функцию подключения, чтобы вернуть только неизмененный компонент, который был ей передан.

Например, в вашем приложении может быть компонент:


export class ExampleApp extends Component {
  render() {
  }
}

export default connect(
  null,
  { someAction }
)(ExampleApp);

затем в начале вашего тестового файла вы можете поместить имитацию функции подключения, чтобы остановить взаимодействие Redux с вашим компонентом:

jest.mock("react-redux", () => {
  return {
    connect: (mapStateToProps, mapDispatchToProps) => (
      ReactComponent
    ) => ReactComponent
  };
});

Этот макет нужно будет поместить в каждый тестовый файл, который монтирует подключенный дочерний элемент.

@scottbanyard, как передать реквизиты дочернему компоненту?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги