I am trying to test my simple component (previous toggle, today named Switch in material-ui library).
I have this wrapped into:
class AutoRefreshSwitch extends React.Component {
constructor(props) {
super(props);
this.input = null;
}
handleChange = () => {
console.log('handler ')
this.props.onAutoRefreshClick(!this.props.autoRefreshStatus);
};
render() {
const {classes} = this.props;
return (
<FormControlLabel
control={
<Switch
checked={this.props.autoRefreshStatus}
onChange={this.handleChange}
color="primary"
classes={{
switchBase: classes.switchBase,
checked: classes.checked,
colorPrimary: classes.colorPrimary,
bar: classes.bar,
icon: classes.icon,
root: classes.root,
}}
disableRipple
inputProps={{id: "switch12345"}}
/>
}
label="Auto refresh"
classes={{label: classes.label}}
/>
);
}
}
export default withStyles(styles)(AutoRefreshSwitch);
This component is placed like it:
<Container> -> it has mapToState and mapToProps with this onAutoRefreshClick which is passed as a prop to component and then to AutoRefreshSwitch
<Component>
<AutoRefreshSwitch onAutoRefreshClick={onAutoRefreshClick}
autoRefreshStatus={autoRefreshStatus}
/>
Now my test is:
import {applyMiddleware, combineReducers, createStore} from 'redux';
import thunk from 'redux-thunk';
import React from 'react';
import {Provider} from 'react-redux';
import {configure, mount} from 'enzyme';
import {myReducer} from "../../src/reducers/overview";
import AutoRefreshSwitch from "../../src/blahblah/auto-refresh-switch";
import Adapter from 'enzyme-adapter-react-16';
import {setAutoRefreshStatus} from "../../src/actions/overview";
// from https://medium.freecodecamp.org/real-integration-tests-with-react-
// redux-and-react-router-417125212638
export function setupIntegrationTest(reducers, initialRouterState = {}) {
const dispatchSpy = jest.fn(() => ({}));
const reducerSpy = (state, action) => dispatchSpy(action);
const emptyStore = applyMiddleware(thunk)(createStore);
const combinedReducers = combineReducers({
reducerSpy,
...reducers,
});
const store = emptyStore(combinedReducers);
return { store, dispatchSpy };
}
configure({adapter: new Adapter()});
describe('integration tests', () => {
let store;
let dispatchSpy;
let wrapper;
beforeEach(() => {
({store, dispatchSpy} = setupIntegrationTest({myReducer}));
wrapper = mount(
<Provider store={store}>
<AutoRefreshSwitch onAutoRefreshClick={setAutoRefreshStatus}
autoRefreshStatus={store.getState().myReducer.autoRefreshStatus}
/>
</Provider>)
});
it('should change the status', () => {
wrapper.find('#switch12345').simulate('change');
wrapper.update();
expect(store.getState().myReducer.autoRefreshStatus).toBe(false)
});
});
Now problem is that, code goes to handleChange in AutoRefreshSwitch but it does not invoke rest of code (this.props.onAutoRefreshClick is not triggered)
I wonder if it is because I don't mount the parents of AutoRefreshSwitch.
This was supposed to be integration test inspired by
https://medium.freecodecamp.org/real-integration-tests-with-react-redux-and-react-router-417125212638
Thanks in advance for any help :)
There was missing dispatch in
beforeEach(() => {
({store, dispatchSpy} = setupIntegrationTest({myReducer}));
wrapper = mount(
<Provider store={store}>
<AutoRefreshSwitch onAutoRefreshClick={() => store.dispatch(setAutoRefreshStatus)}
autoRefreshStatus={store.getState().myReducer.autoRefreshStatus}
/>
</Provider>)
});
Related
The issue it produces is it won't successfully render the MyComponent to the Mock DOM. console.log store displays the correct state in the store as well. But it's just rendering the empty in the body tag.
import React from 'react';
import configureMockStore from 'redux-mock-store';
import * as actions from 'store/reducer/reducer';
import { fireEvent, render, screen } from 'testUtils';
import MyComponent from 'components/MyComponent';
import { initialState } from 'store/reducer/reducer';
const mockStore = configureMockStore();
describe('my component', () => {
let message;
beforeAll(() => {
message = 'testing';
});
it('test 1', () => {
const store = mockStore({
myState: {
...initialState,
message,
},
});
render(<MyComponent />, {
store: store,
});
screen.debug();
expect(screen.queryAllByText(message).length).toBe(1);
});
});
// in testUtils
function render(ui, { store = configureStore(), ...renderOptions } = {}) {
function Wrapper({ children }) {
return (
// ..some other Providers
<Provider store={store}>
{children}
</Provider>
);
}
export {render};
now the screen.debug() only shows
<body>
<div />
</body>
// in MyComponent
const MyComponent = (): JSX.Element => {
const dispatch = useDispatch();
const myState = useSelector(myReducer);
return (
<AnotherComponent
isOpen={myState?.isOpen}
message={myState?.message}
/>
);
};
I found the reason why it's not working. It's because, in myComponent, I had a typo in the conditional statement. Thanks, everyone.
This is my first time testing a react component with redux store related stuff and really confused on pretty much everything.
Tried all google research and still not knowing what to do.
The component looks like below and got two errors in testing console
TypeError: (0 , _store.configureStore) is not a function
TypeError: Cannot read property 'getState' of undefined
the component:
import PropTypes from 'prop-types';
import __includes from 'lodash/includes';
import __isEmpty from 'lodash/isEmpty';
import __lowerCase from 'lodash/lowerCase';
import __toLower from 'lodash/toLower';
import { connect } from 'react-redux';
import { setEmailErrorClass, setPasswordErrorClass, setConfirmPasswordErrorClass } from '../../../redux/actions';
import './TextboxWrapper.scss';
const { notify } = require('../../../services/functions');
class Textbox extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defaultValue !== undefined ? this.props.defaultValue : '',
name: this.props.name,
errorClass: '',
errorMessage: ''
};
this.checkErrors = this.checkErrors.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.isError) {
this.setState({ errorClass: 'error' });
}
}
// ...some functions...
render() {
return (
<div data-type={this.props.type} className={`textbox-wrapper ${this.state.errorClass}`}>
{
this.props.type === 'payment' ? (
<div id={this.props.id} className={this.props.extraClass}></div>
) : (
<input
type={this.props.type}
placeholder={this.props.placeholder}
onChange={e => this.onChange(e)}
onBlur={this.checkErrors}
value={this.state.value}
name={this.props.name}
min={this.props.min}
max={this.props.max}
onFocus={this.props.onFocus}
/>
)
}
</div>
);
}
}
const mapDispatchToProps = { setEmailErrorClass, setPasswordErrorClass, setConfirmPasswordErrorClass };
export default connect(null, mapDispatchToProps)(Textbox);
Below is configureStore:
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit';
import rootReducer from './rootReducer';
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware({
serializableCheck: false
})
});
export default store;
Below is Textbox.test.js:
import React from 'react';
import { Textbox } from './index';
import { Provider } from 'react-redux';
import { mount, shallow } from 'enzyme';
import { configureStore } from '../../../redux/store';
import { browserHistory } from 'react-router';
describe('Textbox', function () {
const baseProps = {[enter image description here][1]
errorClass: 'nope',
};
let store;
beforeAll(() => {
store = configureStore({}, browserHistory);
});
it('renders a wrapping div with accurate className received from props', () => {
const wrapper = mount(
<Provider store={store}>
<Textbox {...baseProps} />
</Provider>
);
const selectWrapper = wrapper.find('div');
expect(
selectWrapper.hasClass(`textbox-wrapper ${baseProps.errorClass}`)
).toEqual(true);
});
Got two errors in console:
[1]: https://i.stack.imgur.com/D0hWk.jpg
'../../../redux/store' doesn't appear to export a configureStore function:
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit';
import rootReducer from './rootReducer';
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware({
serializableCheck: false
})
});
export default store;
It default exports the store object.
I'm guessing you either want to import and use the configured store object in your test:
import store from '../../../redux/store';
import { browserHistory } from 'react-router';
describe('Textbox', function () {
const baseProps = {
errorClass: 'nope',
};
it('renders a wrapping div with accurate className received from props', () => {
const wrapper = mount(
<Provider store={store}>
<Textbox {...baseProps} />
</Provider>
);
const selectWrapper = wrapper.find('div');
expect(
selectWrapper.hasClass(`textbox-wrapper ${baseProps.errorClass}`)
).toEqual(true);
});
...
}
Or you want to import configureStore from '#reduxjs/toolkit' and import your reducers and instantiate the store object in the test. This ends up effectively the same as above.
import { configureStore } from '#reduxjs/toolkit';
import { browserHistory } from 'react-router';
import rootReducer from './rootReducer';
describe('Textbox', function () {
const baseProps = {[enter image description here][1]
errorClass: 'nope',
};
let store;
beforeAll(() => {
store = configureStore({
reducer: rootReducer,
});
});
it('renders a wrapping div with accurate className received from props', () => {
const wrapper = mount(
<Provider store={store}>
<Textbox {...baseProps} />
</Provider>
);
const selectWrapper = wrapper.find('div');
expect(
selectWrapper.hasClass(`textbox-wrapper ${baseProps.errorClass}`)
).toEqual(true);
});
...
}
I want to write test cases for HeaderBar.jsx in the file HeaderBar.spec.jsx.
First I want to write test case for checking my history.push() is executing call then I want to check for history.push() executing with mockURL.
history.push() is found in onLogout()
here is HeaderBar.jsx
import React from 'react';
import { PropTypes } from 'prop-types';
import { bindActionCreators } from 'redux';
import { makeStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/icons/Menu';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import { ROUTE_CONSTANTS, I18N_CONSTANTS } from '../../../../constants';
import { commonAction, sessionAction } from '../../../../redux/actions';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1
},
menuButton: {
marginRight: theme.spacing(2)
},
title: {
flexGrow: 1
}
}));
const HeaderBar = ({ history, t, actions }) => {
const classes = useStyles();
const onLogout = () => {
history.push(ROUTE_CONSTANTS.MEMBER.LOGOUT);
actions.logout();
};
// const onLogout = () => { throw new Error('Failed to run') };
return (
<div className={classes.root}>
<AppBar position="static" color="default">
<Toolbar>
<IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
{t('headerBar.title')}
</Typography>
<Button color="inherit" onClick={onLogout}>
{t('headerBar.logout')}
</Button>
</Toolbar>
</AppBar>
</div>
);
};
HeaderBar.defaultProps = {
t: () => {},
history: {
push: () => {}
}
};
const mapStateToProps = state => {
return {
isLoading: state.common.loadingIndicator.isLoading
};
};
const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators({ ...commonAction, ...sessionAction }, dispatch)
};
};
HeaderBar.propTypes = {
actions: PropTypes.shape({
logout: PropTypes.func.isRequired
}).isRequired,
t: PropTypes.func,
history: PropTypes.shape({
push: PropTypes.func
})
};
const component = connect(mapStateToProps, mapDispatchToProps)(HeaderBar);
export default withTranslation(I18N_CONSTANTS.NAMESPACE.APP)(component);
HeaderBar.spec.jsx
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import configureStore from '../../../../redux/store/configureStore';
import HeaderBar from './HeaderBar';
const setup = (props = {}) => {
const store = configureStore();
window.location.replace = jest.fn();
const defaultProps = {
isLoading: false,
history: {
replace: jest.fn(x => x)
},
...props
};
return mount(
<Provider store={store}>
<HeaderBar {...defaultProps} />
</Provider>
);
};
describe('HeaderBar component', () => {
it('should call translation 2 times', () => {
const props = { t: jest.fn() };
const wrapper = setup(props);
const { t, isLoading } = wrapper.find('HeaderBar').props();
expect(t).toHaveBeenCalledTimes(2);
expect(isLoading).toBeFalsy();
});
it('should call history on onClick event', () => {
const props = { history: { replace: jest.fn(x => x) }, onLogout: jest.fn(x => x) };
const wrapper = setup(props);
// console.log(wrapper.find('button').debug());
wrapper.find('button').simulate('click');
expect(props.history.replace).toHaveBeenCalledTimes(1);
});
it('should call history with mock URL', () => {
const props = { history: { replace: jest.fn(x => x) } };
const wrapper = setup(props);
const mockURL = '/';
wrapper
.find('button')
.at(0)
.simulate('click');
expect(props.history.replace).toHaveBeenCalledWith(mockURL);
});
});
should call history on onClick event and should call history with mock URL is not working for me.
please help me out on this.
You mock history.replace, but in your component you use history.push.
You mock your functions’ implementations as (x => x), but these functions actually don’t receive any arguments.
Is your logout button really the only in this wrapper’s DOM? Maybe add some id?
import React from 'react';
import { Breadcrumb as AntBreadcrumb, Breadcrumb } from 'antd';
import './breadcrumb.scss';
import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
import { updateBreadcrumb } from './../../../redux/actions/baseLayout';
import { connect } from 'react-redux';
import { HomeOutlined, RightOutlined } from '#ant-design/icons';
interface NewProps {
breadcrumb: any;
}
type Props = NewProps & RouteComponentProps<{}>;
// #TODO
class Render extends React.Component<Props> {
state = {
routes: [{ path: '', breadcrumbName: '' }]
};
// eslint-disable-next-line react/no-deprecated
componentWillReceiveProps(nextProps: any) {
this.setState({
routes: this.props.breadcrumb
});
}
componentDidMount = () => {
this.setState({
routes: this.props.breadcrumb
});
};
itemRender = (route: any, params: any, routes: any, paths: any) => {
const last = routes.indexOf(route) === routes.length - 1;
return last ? (
<span>{route.breadcrumbName}</span>
) : (
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
);
};
shouldComponentUpdate = () => {
return true;
};
componentDidUpdate = (prevProps: RouteComponentProps) => {
if (prevProps !== this.props) {
this.setState({
routes: this.props.breadcrumb
});
}
};
render() {
return (
<div style={{ color: 'red' }}>
<Breadcrumb
style={{ fontWeight: 600, fontSize: '15px', color: 'black' }}
separator={
<RightOutlined
style={{ transform: 'scalex(0.7)', fontSize: '16px' }}
/>
}
>
{this.state.routes.map((route, index) => {
return (
<Breadcrumb.Item key={index} href={`${route.path}`}>
{`${route.breadcrumbName}`}
{/* <HomeOutlined /> */}
</Breadcrumb.Item>
);
})}
</Breadcrumb>
</div>
);
}
}
const mapDispatchToProps = {
updateBreadcrumb: updateBreadcrumb
};
const mapStateToProps = (state: any) => {
return {
breadcrumb: state.breadcrumb.breadcrumb || []
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Render));
above code i wanted to write test case but i am not able to mount it i tried to mount it with just but it's not creating snapshot it's givingtest case fail please guide me to right direction or some documentations i am really new to raect and my company gave me this assignment to write test case but i am not finding anywhere any relevent documentations.
import React from 'react';
import Enzyme, { shallow, mount } from 'enzyme';
import Componnent from '../breadcrumb';
import { BrowserRouter as Router } from 'react-router-dom';
import configureStore from 'redux-mock-store'; //ES6 modules
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import renderer from 'react-test-renderer';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({
adapter: new Adapter()
});
const setUp = (initprops:any) => {
const wrapper = mount(<Router><Provider store={initprops}><Componnent /></Provider></Router>);
return wrapper;
};
describe('Login Component', () => {
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
const initialState = {};
const props = mockStore(initialState);
let wrapper:any,instancewrapper:any;
beforeEach(() => {
wrapper = setUp(props);
instancewrapper = wrapper.instance();
});
it('should render correctly', () => {
const tree = renderer.create(<Router><Provider store={props}><Componnent /></Provider></Router>).toJSON();
expect(tree).toMatchSnapshot();
});
});
You can still shallow render a component and use .dive() function to unwrap the main component.
I'm using Jest and Enzyme to write unit tests. At one of my React components I render two distinct redux forms.
A simplified version of it would be like:
// my_app.js
import React from 'react'
import MyForm from './my_form'
import MyOtherForm from './my_other_form'
class MyApp extends React.PureComponent {
render() {
return (
<div>
<MyForm />
<MyOtherForm />
</div>
)
}
}
// my_form.js
let MyForm = () => <form className='my-form' />
MyForm = reduxForm({ form: 'myForm' })(MyForm)
export default connect(state => state)(MyForm)
// my_other_form.js
let MyOtherForm = () => <form className='my-other-form' />
MyOtherForm = reduxForm({ form: 'myOtherForm' })(MyOtherForm)
export default connect(state => state)(MyOtherForm)
Write a simple test just to console log a shallow rendering would look like:
// test.js
import React from 'react'
import { shallow } from 'enzyme'
import MyApp from './my_app'
describe('<MyApp />'), () => {
let wrapper = shallow(<MyApp />)
it('should test something', () => {
console(wrapper.debug())
})
}
Would log something like:
<div>
<Connect(ReduxForm) />
<Connect(ReduxForm) />
</div>
How can I assert that both MyForm and MyOtherForm were rendered correctly?
Thanks!
You should use provider.
I use 'mount' instead of 'shallow' when using Provider.
Here is an example.
import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import LoginForm from '../LoginForm';
const mockStore = configureMockStore();
const dispatch = jest.fn();
const handleSubmit = jest.fn();
const onSubmit = jest.fn();
let store;
const initProps = {
onSubmit,
};
describe('LoginForm', () => {
beforeAll(() => {
store = mockStore({
handleSubmit,
dispatch,
});
dispatch.mockClear();
});
it('renders without exploding', () => {
const wrapper = mount(
<Provider store={store}>
<LoginForm {...initProps} />
</Provider>,
);
expect(wrapper).toHaveLength(1);
expect(wrapper.find('button').prop('disabled')).toBe(true);
});
});
Otherwise, you can wrap your form simply.
// LoginForm.jsx
const LoginForm = (props) => (
<form>
...
</form>
);
// LoginFormWrapper.jsx
import { reduxForm } from 'redux-form';
import LoginForm from './LoginForm';
export default reduxForm({
form: 'loginForm',
})(LoginForm);
// index.js
export { default } from './LoginFormWrapper';
//test.spec.js
import LoginForm from './LoginForm';
describe('LoginForm', () => {
const wrapper = shallow(<LoginForm />);
expect(wrapper).toHaveLength(1);
});