Как Unit Test Связанные с React-Redux компоненты?
Я использую Mocha, Chai, Karma, Sinon, Webpack для модульных тестов.
Я перешел по этой ссылке, чтобы настроить среду тестирования для кода React-Redux.
Как реализовать тестирование + покрытие кода в React с помощью Karma, Babel и Webpack
Я могу успешно протестировать код JavaScript для своих действий и редукторов, но когда дело доходит до тестирования моих компонентов, всегда выдается какая-то ошибка.
import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils'; //I like using the Test Utils, but you can just use the DOM API instead.
import chai from 'chai';
// import sinon from 'sinon';
import spies from 'chai-spies';
chai.use(spies);
let should = chai.should()
, expect = chai.expect;
import { PhoneVerification } from '../PhoneVerification';
let fakeStore = {
'isFetching': false,
'usernameSettings': {
'errors': {},
'username': 'sahil',
'isEditable': false
},
'emailSettings': {
'email': '[email protected]',
'isEmailVerified': false,
'isEditable': false
},
'passwordSettings': {
'errors': {},
'password': 'showsomestarz',
'isEditable': false
},
'phoneSettings': {
'isEditable': false,
'errors': {},
'otp': null,
'isOTPSent': false,
'isOTPReSent': false,
'isShowMissedCallNumber': false,
'isShowMissedCallVerificationLink': false,
'missedCallNumber': null,
'timeLeftToVerify': null,
'_verifiedNumber': null,
'timers': [],
'phone': '',
'isPhoneVerified': false
}
}
function setup () {
console.log(PhoneVerification);
// PhoneVerification.componentDidMount = chai.spy();
let output = TestUtils.renderIntoDocument(<PhoneVerification {...fakeStore}/>);
return {
output
}
}
describe('PhoneVerificationComponent', () => {
it('should render properly', (done) => {
const { output } = setup();
expect(PhoneVerification.prototype.componentDidMount).to.have.been.called;
done();
})
});
Следующая ошибка связана с приведенным выше кодом.
FAILED TESTS:
PhoneVerificationComponent
✖ should render properly
Chrome 48.0.2564 (Mac OS X 10.11.3)
Error: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
Пробовал переключаться с шпионов-синонов на чай-шпионов.
Как мне выполнить модульное тестирование подключенных компонентов React-Redux (Smart Components)?
Ответы
Ответ 1
Более красивый способ сделать это - экспортировать как ваш простой компонент, так и компонент, завернутый в соединение. Именованным экспортом будет компонент, по умолчанию это завернутый компонент:
export class Sample extends Component {
render() {
let { verification } = this.props;
return (
<h3>This is my awesome component.</h3>
);
}
}
const select = (state) => {
return {
verification: state.verification
}
}
export default connect(select)(Sample);
Таким образом, вы можете импортировать обычно в свое приложение, но когда дело доходит до тестирования, вы можете импортировать свой именованный экспорт, используя import { Sample } from 'component'
.
Ответ 2
Вы можете протестировать свой подключенный компонент, и я думаю, вы должны это сделать. Сначала вы можете протестировать несвязанный компонент, но я предлагаю вам не иметь полного охвата тестирования, не тестируя также подключенный компонент.
Ниже приведен непроверенный экстракт того, что я делаю с Redux и Enzyme. Основная идея заключается в использовании Провайдера для подключения состояния в тесте к подключенному компоненту в тесте.
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import SongForm from '../SongForm'; // import the CONNECTED component
// Use the same middlewares you use with Redux applyMiddleware
const mockStore = configureMockStore([ /* middlewares */ ]);
// Setup the entire state, not just the part Redux passes to the connected component.
const mockStoreInitialized = mockStore({
songs: {
songsList: {
songs: {
songTags: { /* ... */ }
}
}
}
});
const nullFcn1 = () => null;
const nullFcn2 = () => null;
const nullFcn3 = () => null;
const wrapper = mount( // enzyme
<Provider store={store}>
<SongForm
screen="add"
disabled={false}
handleFormSubmit={nullFcn1}
handleModifySong={nullFcn2}
handleDeleteSong={nullFcn3}
/>
</Provider>
);
const formPropsFromReduxForm = wrapper.find(SongForm).props(); // enzyme
expect(
formPropsFromReduxForm
).to.be.deep.equal({
screen: 'add',
songTags: initialSongTags,
disabled: false,
handleFormSubmit: nullFcn1,
handleModifySong: nullFcn2,
handleDeleteSong: nullFcn3,
});
===== ../SongForm.js
import React from 'react';
import { connect } from 'react-redux';
const SongForm = (/* object */ props) /* ReactNode */ => {
/* ... */
return (
<form onSubmit={handleSubmit(handleFormSubmit)}>
....
</form>
};
const mapStateToProps = (/* object */ state) /* object */ => ({
songTags: state.songs.songTags
});
const mapDispatchToProps = () /* object..function */ => ({ /* ... */ });
export default connect(mapStateToProps, mapDispatchToProps)(SongForm)
Возможно, вы захотите создать магазин с чистым Redux. redux-mock-store - это просто облегченная версия, предназначенная для тестирования.
Возможно, вы захотите использовать add-addons-test-utils вместо Airbnb Enzyme.
Я использую cha-фермент airbnb, чтобы иметь варианты ожидающего реагирования. В этом примере он не нужен.
Ответ 3
Проблема с принятым ответом состоит в том, что мы экспортируем что-то излишне, просто чтобы иметь возможность проверить это. И экспортировать класс просто для проверки, на мой взгляд, не очень хорошая идея.
Вот более точное решение без необходимости экспорта чего-либо, кроме подключенного компонента:
Если вы используете Jest, вы можете смоделировать метод connect
, чтобы получить три вещи:
- mapStateToProps
- mapDispatchToProps
- ReactComponent
Сделать это довольно просто. Есть 2 способа: встроенные или глобальные.
1. Использование встроенного макета
Добавьте следующий фрагмент перед функцией описания теста.
jest.mock('react-redux', () => {
return {
connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({
mapStateToProps,
mapDispatchToProps,
ReactComponent
}),
Provider: ({ children }) => children
}
})
Ответ 4
Попробуйте создать 2 файла, один с самим компонентом, не осведомленный ни о каком магазине или что-либо (PhoneVerification-component.js). Затем второй (PhoneVerification.js), который вы будете использовать в своем приложении и который возвращает только первый компонент, подписанный для хранения через функцию connect
, что-то вроде
import PhoneVerificationComponent from './PhoneVerification-component.js'
import {connect} from 'react-redux'
...
export default connect(mapStateToProps, mapDispatchToProps)(PhoneVerificationComponent)
Затем вы можете протестировать свой "тупой" компонент, потребовав PhoneVerification-component.js
в своем тесте и предоставив ему необходимые издевательства. Тестирование уже не тестировалось (соедините декоратор, mapStateToProps, mapDispatchToProps и т.д.)