Я пытаюсь выполнить модульный тест в форме входа, имитируя события для ввода неправильных значений в текстовые поля, чтобы в тесте были ошибки. У меня есть следующие настройки:
SignInPage. js
import {CssBaseline, withStyles} from '@material-ui/core';
import Avatar from '@material-ui/core/Avatar'
import Button from '@material-ui/core/Button';
import Checkbox from '@material-ui/core/Checkbox';
import Container from '@material-ui/core/Container';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Grid from '@material-ui/core/Grid';
import TextField from '@material-ui/core/TextField';
import Typography from '@material-ui/core/Typography';
import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
import {Formik} from 'formik';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import * as yup from 'yup';
import {actions} from '../../../_actions/user.actions';
import Footer from '../../../_components/footer/footer';
import Navbar from '../../../_components/navbar/navbar';
import {history} from '../../../_helpers/history';
import config from '../../../config';
const useStyles = theme => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
main: {
marginTop: theme.spacing(8),
marginBottom: theme.spacing(2),
alignItems: 'center'
},
footer: {
padding: theme.spacing(3, 2),
marginTop: 'auto'
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
}
});
export class SignInPage extends Component {
constructor(props) {
super(props);
// reset login status
this.props.signOut();
this.state = {
values: {
email: '',
password: '',
remember: undefined
}
};
this.validationSchema = yup.object().shape({
email: yup.string().email().required('Please enter email address'),
password: yup.string().required('Please enter password'),
});
this.validateAndSubmit = this.validateAndSubmit.bind(this);
}
validateAndSubmit(values, {setSubmitting}) {
this.validationSchema.isValid(values)
.then(valid => {
if (valid) {
this.props.signIn(values);
}
})
}
navigateToForgotPass(e) {
history.push('/forgot-password');
};
componentDidUpdate() {
this.isLoggedIn = this.props.isLoggedIn && this.props.tokens !== null;
if (this.isLoggedIn) history.push('/home');
}
render() {
const {classes, isFetching} = this.props;
return (
<React.Fragment>
<CssBaseline/>
<Navbar appName={config.appName} data-test="navbarComponent"/>
<Container component="main" maxWidth="xs" data-test="signInContainer">
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon/>
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Formik initialValues={{email: '', password: ''}}
validationSchema={this.validationSchema}
onSubmit={this.validateAndSubmit} data-test="formik">
{({
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
}) => (
<form className={classes.form} onSubmit={handleSubmit} noValidate data-test="signinForm">
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
onChange={handleChange}
onBlur={handleBlur}
disabled={isFetching}
error={errors.email && touched.email}
helperText={(errors.email && touched.email) && errors.email}
data-test="emailInput"
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
onChange={handleChange}
onBlur={handleBlur}
disabled={isFetching}
error={errors.password && touched.password}
helperText={(errors.password && touched.password) && errors.password}
data-test="passwordInput"
/>
<FormControlLabel
control={<Checkbox color="primary" id="remember" name="remember" disabled={isFetching} onChange={handleChange}/>}
label="Remember me"
data-test="rememberMe"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={isFetching}
data-test="submitButton"
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Button color="primary" size="small" onClick={this.navigateToForgotPass} data-test="forgotPassword">Forgot password?</Button>
</Grid>
</Grid>
</form>
)}
</Formik>
</div>
</Container>
{/* Footer */}
<footer className={classes.footer}>
<Footer appName={config.appName} data-test="footerComponent"/>
</footer>
{/* End footer */}
</React.Fragment>
);
}
}
SignInPage.propTypes = {
classes: PropTypes.object.isRequired
};
function mapState(state) {
const {isLoggedIn, tokens, isFetching} = state.user;
return {isLoggedIn, tokens, isFetching};
}
const actionCreators = {
signOut: actions.signOut,
signIn: actions.signIn
};
const connectedSignInPage = connect(mapState, actionCreators)(SignInPage);
export default withStyles(useStyles)(connectedSignInPage);
SignInPage.test. js
import TextField from '@material-ui/core/TextField';
import {mount, shallow} from 'enzyme';
import React from 'react';
import {Provider} from 'react-redux'
import {userConstants} from '../../../_constants';
import {checkProps, findByDataAttr, findByDataAttrWhenMounted, testStore} from '../../../_utils';
import SignInPage from './SignInPage';
const props = {
classes: {paper: 'someprop'},
};
const setUp = (props = {}) => {
return shallow(<SignInPage {...props}/>);
};
const setUpWithStore = (props = {}, initialStates = {}) => {
const store = testStore(initialStates);
return mount(
<Provider store={store}>
<SignInPage {...props}/>
</Provider>
);
};
describe('Checking Components', () => {
let component;
beforeEach(() => {
const initialStates = {};
component = setUpWithStore(props, initialStates);
});
it.only('should show error when nothing entered', () => {
const emailInput = component.find(TextField).at(0);
emailInput.simulate('blur', {target: {value: '20'}});
console.log(emailInput.props());
expect(emailInput.props().error).toBe(true);
});
});
});
Utils. js
import checkPropTypes from 'check-prop-types';
import {applyMiddleware, createStore} from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './../_reducers';
const findByDataAttr = (component, attr) => {
return component.find(`[data-test='${attr}']`);
};
const findByDataAttrWhenMounted = (component, attr) => {
return component.find(`[data-test='${attr}']`).hostNodes();
};
const checkProps = (component, expectedProps) => {
return checkPropTypes(component.propTypes, expectedProps, 'props', component.name);
};
const testStore = (initialState) => {
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
return createStoreWithMiddleware(rootReducer, initialState);
};
module.exports = {findByDataAttr, checkProps, testStore, findByDataAttrWhenMounted};
Результат теста:
{
variant: 'outlined',
margin: 'normal',
required: true,
fullWidth: true,
id: 'email',
label: 'Email Address',
name: 'email',
autoComplete: 'email',
onChange: [Function],
onBlur: [Function],
disabled: undefined,
error: undefined,
helperText: undefined,
'data-test': 'emailInput'
}
Error: expect(received).toBe(expected) // Object.is equality
Expected: true
Received: undefined
Кажется, что error
не определено. Есть ли обходной путь для этой ситуации?