我正在学习如何使用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)

尝试测试我的reducer时会发生同样的错误 . 值得注意的是,我正在使用与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;
    };

Any ideas?