Привет, это скорее вопрос, чем проблема.
Недавно я начал использовать React и Redux для проекта, и у меня возникла пара проблем с тестированием, но документация, похоже, не дает окончательного решения проблемы.
В настоящее время у меня есть подключенный компонент, для которого я хотел бы написать несколько модульных тестов, однако есть подключенный компонент, вложенный в первый компонент, и я, похоже, в конечном итоге блокирую свойства или состояние (или в большинстве случаев оба) из вложенный компонент не устанавливается.
Мне было интересно, есть ли способ заглушить один компонент, чтобы я мог сосредоточиться только на компоненте, который пытаюсь протестировать?
заранее спасибо
Две вещи:
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"
при запуске тестов:
NODE_ENV='test' mocha app/**/*.test.jsx
.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, как передать реквизиты дочернему компоненту?
Самый полезный комментарий
Здесь возникает проблема, когда тестируемый компонент затем отображает другие подключенные компоненты либо как прямые дочерние элементы, либо где-то ниже по иерархии. Если вы выполняете полный рендеринг через
mount()
, тогда подключенные компоненты-потомки будут пытаться получить доступ кthis.context.store
и не найдут его. Итак, основные варианты - либо убедиться, что потомки не отображаются, выполнив неглубокий тест, либо фактически сделать хранилище доступным в контексте.