开玩笑 运行 时,应用程序不会在 history.push 上重新呈现

App not re-rendering on history.push when run with jest

我正在尝试使用 jest 和 react-testing-library 测试我的 LoginForm 组件。成功提交登录表单后,我的 handleLoginSuccess 函数应该在 localStorage 上设置 'user' 项并使用 history.push() 将用户导航回主页。这在我的开发环境中的浏览器中有效,但是当我使用 Jest 渲染组件并模拟 API 时,localStorage 会更新但不会导航到“/”。

我试过在调用 history.push() 之前设置 localStorage。我不确定在这种情况下是什么负责重新渲染,以及为什么它在开发中有效但在测试中无效。

Login.test.jsx

import 'babel-polyfill'
import React from 'react'
import {withRouter} from 'react-router'
import {Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'
import {render, fireEvent} from '@testing-library/react'
import Login from '../../pages/Login'
import API from '../../util/api'

jest.mock('../../util/api')


function renderWithRouter(
  ui,
  {route = '/', history = createMemoryHistory({initialEntries: [route]})} = {},
) {
  return {
    ...render(<Router history={history}>{ui}</Router>),
    // adding `history` to the returned utilities to allow us
    // to reference it in our tests (just try to avoid using
    // this to test implementation details).
    history,
  }
}

describe('When a user submits the login button', () => {
  test('it allows the user to login', async () => {
    const fakeUserResponse = {'status': 200, 'data': { 'user': 'Leo' } }

    API.mockImplementation(() => {
      return {
        post: () => {
          return Promise.resolve(fakeUserResponse)
        }
      }
    })

    const route = '/arbitrary-route'
    const {getByLabelText, getByText, findByText} = renderWithRouter(<Login />, {route})

    fireEvent.change(getByLabelText(/email/i), {target: {value: 'email@gmail.com '}})
    fireEvent.change(getByLabelText(/password/i), {target: {value: 'Foobar123'}})
    fireEvent.click(getByText(/Log in/i))

    const logout = await findByText(/Log Out/i)

    expect(JSON.parse(window.localStorage.getItem('vector-user'))).toEqual(fakeUserResponse.data.user)
  })
})

LoginForm.jsx

的相关部分
class LoginForm extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      disableActions: false,
      formErrors: null,
    };
  }

  handleLoginSuccess = () => {
    const { loginSuccessCallback, redirectOnLogin, history } = { ...this.props };

    if (loginSuccessCallback) {
      loginSuccessCallback();
    } else {
      history.push('/');
    }
  }

  loginUser = ({ user }) => {
    localStorage.setItem('vector-user', JSON.stringify(user));
  }

  handleLoginResponse = (response) => {
    if (response.status !== 200) {
      this.handleResponseErrors(response.errors);
    } else {
      this.loginUser(response.data);
      this.handleLoginSuccess();
    }
  }

  handleLoginSubmit = (event) => {
    event.preventDefault();

    const {
      disableActions, email, password
    } = { ...this.state };

    if (disableActions === true) {
      return false;
    }

    const validator = new Validator();
    if (!validator.validateForm(event.target)) {
      this.handleResponseErrors(validator.errors);
      return false;
    }

    this.setState(prevState => ({ ...prevState, disableActions: true }));
    new API().post('login', { email, password }).then(this.handleLoginResponse);

    return true;
  }
}

Login.jsx

import React from 'react';
import { withRouter, Link } from 'react-router-dom';
import PropTypes from 'prop-types';

import LoginForm from '../components/LoginForm';

class Login extends React.Component {
  constructor({ location }) {
    super();

    const originalRequest = location.state && location.state.originalRequest;
    this.state = {
      originalRequest
    };
  }

  render() {
    const { originalRequest } = { ...this.state };

    return (
      <div>
        <h1>Login</h1>
        <LoginForm redirectOnLogin={originalRequest && originalRequest.pathname} />
        <Link to="/forgot">Forgot your password?</Link>
      </div>
    );
  }
}
Login.propTypes = {
  location: PropTypes.shape({
    state: PropTypes.shape({
      originalRequest: PropTypes.shape({
        pathname: PropTypes.string
      })
    })
  })
};

export default withRouter(Login);

当前 await findByText() 超时。

我认为那是因为在您的测试中您没有渲染任何 Route 组件。没有这些,react-router 就无法知道在路由更改时要渲染什么。它将始终呈现 Login.