我有一个React组件,如果它的初始本地状态发生了变化,它会加载另一个组件。我无法进行干净的测试,因为我需要设置本地状态和浅层渲染,以便在新组件安装时子组件不会崩溃,因为redux存储不在那里。似乎这两个目标在酶中是不相容的。
要显示子组件,需要进行以下操作:
这会让测试产生一些令人头疼的问题。以下是确定要渲染内容的实际行:
let correctAnswer = this.props.response ? <div className="global-center"><h4 >{this.props.response}</h4><Score /></div> : <p className="quiz-p"><strong>QUESTION:</strong> {this.props.currentQuestion}</p>;
这是我目前的酶测试:
it('displays score if response and usingQuiz prop give proper input', () => {
const wrapper = shallow(<Quiz usingQuiz={true} answers={[]} response={'example'}/>);
wrapper.setState({ started: true })
expect(wrapper.contains(<Score />)).toEqual(true)
});
我使用的是浅层,因为每当我使用mount时,我都会这样:
Invariant Violation: Could not find "store" in either the context or props of "Connect(Score)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Score)".
因为组件是通过父级显示的,所以我不能简单地选择断开连接的版本。使用浅似乎纠正了这个问题,但后来我无法更新本地状态。当我尝试这个时:
it('displays score if response and usingQuiz prop give proper input', () => {
const wrapper = shallow(<Quiz usingQuiz={true} answers={[]} response={'example'}/>);
wrapper.setState({ started: true })
expect(wrapper.contains(<Score />)).toEqual(true)
});
测试失败,因为浅层没有让DOM更新。
我需要满足两个条件。我可以单独做每个条件,但是当我需要同时1)在组件内部渲染一个组件(需要浅或者会对不在那里的商店感到厌恶),以及2)更新本地状态(需要挂载,而不是浅层),我无法一次性完成所有工作。
我已经看过关于这个话题的聊天,而且这似乎是Enzyme的合法限制,至少在2017年。这个问题是否已得到解决?测试这个非常困难。
如果有人需要它作为参考,这是完整的组件:
import React from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { Transition } from 'react-transition-group';
import { answerQuiz, deleteSession, getNewQuestion } from '../../actions/quiz';
import Score from '../Score/Score';
import './Quiz.css';
export class Quiz extends React.Component {
constructor(props) {
super(props);
// local state for local component changes
this.state = {
started: false
}
}
handleStart() {
this.setState({started: true})
}
handleClose() {
this.props.dispatch(deleteSession(this.props.sessionId))
}
handleSubmit(event) {
event.preventDefault();
if (this.props.correctAnswer && this.props.continue) {
this.props.dispatch(getNewQuestion(this.props.title, this.props.sessionId));
}
else if (this.props.continue) {
const { answer } = this.form;
this.props.dispatch(answerQuiz(this.props.title, answer.value, this.props.sessionId));
}
else {
this.props.dispatch(deleteSession(this.props.sessionId))
}
}
render() {
// Transition styles
const duration = 300;
const defaultStyle = {
opacity: 0,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
height: '100%',
width: '100%',
margin: '0px',
zIndex: 20,
top: '0px',
bottom: '0px',
left: '0px',
right: '0px',
position: 'fixed',
display: 'flex',
alignItems: 'center',
transition: `opacity ${duration}ms ease-in-out`
}
const transitionStyles = {
entering: { opacity: 0 },
entered: { opacity: 1 }
}
// Response text colors
const responseClasses = [];
if (this.props.response) {
if (this.props.response.includes("You're right!")) {
responseClasses.push('quiz-right-response')
}
else {
responseClasses.push('quiz-wrong-response');
}
}
// Answer radio buttons
let answers = this.props.answers.map((answer, idx) => (
<div key={idx} className="quiz-question">
<input type="radio" name="answer" value={answer} /> <span className="quiz-question-label">{answer}</span>
</div>
));
// Question or answer
let correctAnswer = this.props.response ? <div className="global-center"><h4 className={responseClasses.join(' ')}>{this.props.response}</h4><Score /></div>: <p className="quiz-p"><strong>QUESTION:</strong> {this.props.currentQuestion}</p>;
// Submit or next
let button = this.props.correctAnswer ? <button className="quiz-button-submit">Next</button> : <button className="quiz-button-submit">Submit</button>;
if(!this.props.continue) {
button = <button className="quiz-button-submit">End</button>
}
// content - is quiz started?
let content;
if(this.state.started) {
content = <div>
<h2 className="quiz-title">{this.props.title} Quiz</h2>
{ correctAnswer }
<form className="quiz-form" onSubmit={e => this.handleSubmit(e)} ref={form => this.form = form}>
{ answers }
{ button }
</form>
</div>
} else {
content = <div>
<h2 className="quiz-title">{this.props.title} Quiz</h2>
<p className="quiz-p">So you think you know about {this.props.title}? This quiz contains {this.props.quizLength} questions that will test your knowledge.<br /><br />
Good luck!</p>
<button className="quiz-button-start" onClick={() => this.handleStart()}>Start</button>
</div>
}
// Is quiz activated?
if (this.props.usingQuiz) {
return (
<Transition in={true} timeout={duration} appear={true}>
{(state) => (
<div style={{
...defaultStyle,
...transitionStyles[state]
}}>
{/* <div className="quiz-backdrop"> */}
<div className="quiz-main">
<div className="quiz-close" onClick={() => this.handleClose()}>
<i className="fas fa-times quiz-close-icon"></i>
</div>
{ content }
</div>
</div>
)}
</Transition >
)
}
else {
return <Redirect to="/" />;
}
}
}
const mapStateToProps = state => ({
usingQuiz: state.currentQuestion,
answers: state.answers,
currentQuestion: state.currentQuestion,
title: state.currentQuiz,
sessionId: state.sessionId,
correctAnswer: state.correctAnswer,
response: state.response,
continue: state.continue,
quizLength: state.quizLength,
score: state.score,
currentIndex: state.currentIndex
});
export default connect(mapStateToProps)(Quiz);
这是我使用mount的测试(由于缺少存储导致崩溃):
import React from 'react';
import { Quiz } from '../components/Quiz/Quiz';
import { Score } from '../components/Score/Score';
import { shallow, mount } from 'enzyme';
it('displays score if response and usingQuiz prop give proper input', () => {
const wrapper = mount(<Quiz usingQuiz={true} answers={[]} response={'example'}/>);
wrapper.setState({ started: true })
expect(wrapper.contains(<Score />)).toEqual(true)
});
});
答案 0 :(得分:0)
这看起来像应该使用mount(..)
进行测试的组件。
如何导入已连接的组件Score
和Quiz
?
我发现您已正确导出Quiz
组件并默认导出已连接的Quiz
组件。
尝试使用
导入import { Score } from '../Score/Score';
import { Quiz } from '../Quiz/Quiz';
在您的测试中,mount(..)
。如果从默认导出导入,您将获得导入的连接组件,我认为这是导致错误的原因。
答案 1 :(得分:0)
您确定Transition
组件是否允许其显示内容?我使用这个组件,无法在测试中正确处理它...
您是否可以通过以下方式更改render
s return
:
if (this.props.usingQuiz) {
return (
<div>
{
this.state.started && this.props.response ?
(<Score />) :
(<p>No score</p>)
}
</div>
)
}
你的测试看起来像这样:
it('displays score if response and usingQuiz prop give proper input',() => {
const wrapper = shallow(<Quiz usingQuiz={true} answers={[]} response={'example'}/>);
expect(wrapper.find('p').text()).toBe('No score');
wrapper.setState({ started: true });
expect(wrapper.contains(<Score />)).toEqual(true);
});
我还测试了shallow
s setState
,这样的小测试效果很好:
test('HeaderComponent properly opens login popup', () => {
const wrapper = shallow(<HeaderComponent />);
expect(wrapper.find('.search-btn').text()).toBe('');
wrapper.setState({ activeSearchModal: true });
expect(wrapper.find('.search-btn').text()).toBe('Cancel');
});
因此,我认为浅层正确处理setState
以及render
内某些组件导致的问题。
答案 2 :(得分:0)
出现该错误的原因是,您正在尝试测试通过调用connect()()
生成的包装器组件。该包装器组件希望可以访问Redux存储。通常,该存储可以作为context.store使用,因为在组件层次结构的顶部,您将有一个<Provider store={myStore} />
。但是,您将自己渲染连接的组件,而没有存储,因此会引发错误。
此外,如果您要测试组件内部的组件,则可能需要完整的DOM渲染器。
如果您需要强制更新组件,则酶可以帮您。它提供update()
,如果您在对组件的引用上调用update()
,将强制该组件重新呈现自身。