我正在学习如何使用Jest测试我的组件,但是我在测试登录屏幕时遇到了问题。该组件连接到Redux并且是导航器的一部分。我嘲笑我的redux商店,但在运行我的测试时出现以下错误:
FAIL __tests__/screens/Login.test.js
Test suite failed to run
TypeError: Cannot read property 'getStateForAction' of undefined
> 1 | import { NavigationActions } from "react-navigation";
2 | import AppNavigator from "../navigators/AppNavigator";
3 |
4 | const NavigationInitialState = AppNavigator.router.getStateForAction({
at Object.<anonymous> (src/redux/reduxHelpers.js:1:1287)
at Object.<anonymous> (src/redux/reducers/index.js:1:5765)
at Object.<anonymous> (src/redux/store.js:1:2177)
尝试测试我的减速机时发生同样的错误。值得注意的是我使用与redux集成的反应导航。
以下是我的登录屏幕测试代码:
import React from "react";
import { shallow } from "enzyme";
import Login from "../../src/screens/Login";
import mockStore from "../../__mocks__/redux-mock-store";
import { eventInitialState } from "../../src/redux/reducers";
jest.mock("../../src/navigators/AppNavigator", () => ({
AppNavigator: {
router: {
getStateForAction: jest.fn(),
getActionForPathAndParams: jest.fn()
}
}
}));
describe("Testing login screen", () => {
it("renders as expected", () => {
const wrapper = shallow(<Login />, {
context: { store: mockStore(eventInitialState) }
});
expect(wrapper.dive()).toMatchSnapshot();
});
});
这是我的登录界面(大):
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
Alert,
Linking,
ImageBackground,
KeyboardAvoidingView,
Platform,
ScrollView,
Keyboard,
Image
} from "react-native";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { login } from "../redux/actions";
import Button from "../components/Button";
import CustomCheckBox from "../components/CustomCheckBox";
import ErrorBox from "../components/ErrorBox";
import { VersaoAmbiente } from "../utils/helpers";
const recuperarSenhaUrl = "my-url";
const backgroundImage = require("../../assets/images/splash.png");
const logoImage = require("../../assets/images/logo-white-v.png");
const styles = StyleSheet.create({...});
class Login extends Component {
constructor(props) {
super(props);
this.fazerLogin = this.fazerLogin.bind(this);
this.switch = this.switch.bind(this);
this.keyboardDidShow = this.keyboardDidShow.bind(this);
this.keyboardDidHide = this.keyboardDidHide.bind(this);
this.state = {
email: "",
senha: "",
continuarConectado: true,
tecladoVisivel: false
};
}
componentWillMount() {
if (Platform.OS !== "ios") {
this.keyboardDidShowListener = Keyboard.addListener(
"keyboardDidShow",
this.keyboardDidShow
);
this.keyboardDidHideListener = Keyboard.addListener(
"keyboardDidHide",
this.keyboardDidHide
);
}
}
componentWillUnmount() {
if (Platform.OS !== "ios") {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
}
keyboardDidShow() {
this.setState({
tecladoVisivel: true
});
}
keyboardDidHide() {
this.setState({
tecladoVisivel: false
});
}
esqueciMinhaSenha = () => {
Linking.openURL(recuperarSenhaUrl).catch(err => {
Alert.alert("Ops, um erro ocorreu", err, [{ text: "OK" }], {
cancelable: false
});
});
};
fazerLogin() {
const { email, senha, continuarConectado } = this.state;
this.props.login(email.trim(), senha.trim(), continuarConectado);
}
switch(value) {
this.setState({ continuarConectado: value });
}
mensagemErro() {
const { erroLogin, erroNegocio } = this.props;
return erroLogin && erroLogin.response && erroLogin.response.status === 401
? "Email ou senha incorretos"
: erroNegocio && erroNegocio.mensagem
? erroNegocio.mensagem
: "Ops, houve um erro. Tente novamente";
}
render() {
const { erroLogin, logando } = this.props;
return (
<ImageBackground style={styles.container} source={backgroundImage}>
<KeyboardAvoidingView
style={styles.keyboardViewContainer}
behavior={Platform.OS === "ios" ? "padding" : null}
>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.contentScrollView}
scrollEnabled={false}
>
<Image
source={logoImage}
style={{
marginBottom: 20.7 * 3,
width: 155,
height: 125
}}
/>
<TextInput
value={this.state.email}
placeholder="Usuário"
style={[styles.input, { marginBottom: 4 * 3 }]}
placeholderTextColor="#828282"
maxLength={255}
autoCorrect={false}
keyboardType="email-address"
autoCapitalize="none"
returnKeyType="done"
underlineColorAndroid="transparent"
onChangeText={text => this.setState({ email: text })}
/>
<TextInput
value={this.state.senha}
placeholder="Senha"
style={styles.input}
placeholderTextColor="#828282"
maxLength={255}
autoCorrect={false}
autoCapitalize="none"
returnKeyType="done"
secureTextEntry
underlineColorAndroid="transparent"
onChangeText={text => this.setState({ senha: text })}
/>
<View style={styles.esqueceuView}>
<TouchableOpacity onPress={this.esqueciMinhaSenha}>
<Text style={styles.esqueceuSenha}>Esqueceu a senha?</Text>
</TouchableOpacity>
</View>
<CustomCheckBox
style={styles.continuarConectadoView}
onValueChange={this.switch}
value={this.state.continuarConectado}
>
<Text style={styles.continuarConectadoText}>
Manter conectado
</Text>
</CustomCheckBox>
<View style={styles.viewButton}>
<Button
title="ACESSAR SISTEMA"
onPress={() => this.fazerLogin()}
titleStyle={styles.buttonText}
buttonStyle={styles.button}
loading={logando}
/>
</View>
{erroLogin && (
<View style={styles.erroBox}>
<ErrorBox defaultMessage={this.mensagemErro()} />
</View>
)}
</ScrollView>
</KeyboardAvoidingView>
{!this.state.tecladoVisivel && (
<Text style={styles.versao}>{VersaoAmbiente}</Text>
)}
</ImageBackground>
);
}
}
function mapDispatchToProps(dispatch) {
return {
login: (email, senha, continuarConectado) =>
dispatch(login(email, senha, continuarConectado))
};
}
function mapStateToProps(state) {
return {
erroLogin: state.config.erroLogin,
erroNegocio: state.config.erroNegocio,
logando: state.config.logando
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
/* eslint-disable */
Login.propTypes = {
erroLogin: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
erroNegocio: PropTypes.object,
logando: PropTypes.bool.isRequired,
login: PropTypes.func.isRequired
};
最后,我的减速机:
import { NavigationActions } from "react-navigation";
import AppNavigator from "../navigators/AppNavigator";
const NavigationInitialState = AppNavigator.router.getStateForAction({
type: NavigationActions.Init
});
const LoggedInInitialState = AppNavigator.router.getStateForAction(
NavigationActions.reset({
index: 1,
actions: [
NavigationActions.navigate({ routeName: "Splash" }),
NavigationActions.navigate({ routeName: "Drawer" })
]
}),
NavigationInitialState
);
const navReducer = (state = NavigationInitialState, action) => {
let newState;
let logoutSuccessAction;
switch (action.type) {
case LOAD_STATE:
return LoggedInInitialState;
case LOGIN:
return LoggedInInitialState;
case LOGOUT:
logoutSuccessAction = NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: "Login" })]
});
return AppNavigator.router.getStateForAction(logoutSuccessAction, state);
default:
newState = AppNavigator.router.getStateForAction(action, state);
break;
}
return newState || state;
};
有什么想法吗?