【问题标题】:Using Enzyme and Jest to find a className within a component使用 Enzyme 和 Jest 在组件中查找类名
【发布时间】:2020-10-01 01:48:53
【问题描述】:

我正在尝试执行一个简单的单元测试以确认我的Register 组件呈现.container。我认为这可能是shallow 的级别/层次结构问题,或者可能是渲染问题,但我不能确定。

以下是我的Register 组件。

import React, { useState } from 'react';
import { Link, Redirect } from 'react-router-dom';

//redux
import { connect } from 'react-redux';
import { setAlert } from '../../actions/alert';
import { register } from '../../actions/auth';
import PropTypes from 'prop-types';

const Register = ({ setAlert, register, authState: { isAuthenticated } }) => {

  //...

  if (isAuthenticated) {
    return <Redirect to='/dashboard' />;
  }

  return (
    <section className='container'> <====== I want to verify this container so I can do further testing by calling find('input') on the container.
      <h1 className='large text-primary'>Sign up</h1>
      <p className='lead'>
        <i className='fas fa-user-circle'> Create Your Account</i>
      </p>
      <form onSubmit={handleSubmit} className='form'>
        <div className='form-group'>
          <input
            type='text'
            placeholder='Name'
            value={name}
            onChange={e => handleNameChange(e)}
            required
          />
        </div>
       //...
    </section>

Register.propTypes = {
  setAlert: PropTypes.func.isRequired,
  register: PropTypes.func.isRequired,
  authState: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  authState: state.auth 
});

export default connect(mapStateToProps, { setAlert, register })(Register);

这是我的 App 组件,它显示了我的应用程序的层次结构:

const App = () => {
  useEffect(() => {
    if (localStorage.token) {
      setAuthToken(localStorage.token);
      store.dispatch(loadUser());
    }
  }, []); 

  return (
    <Provider store={store}>
      <Router>
        <Fragment>
          <Navbar />
          <Route exact path='/' component={Landing} />
          <section className='container'>
            <Alert />
            <Switch>
              <Route exact path='/Register' component={Register} />
              <Route exact path='/Login' component={Login} />
              //...
     </Provider>
   );

export default App;

以下是我的测试文件,以及我所有尝试使用dive() 是否处于正确级别,或使用不同的酶方法,如find()hasClass() 等。

我尝试在渲染期间显式传递组件所需的道具,并将它们作为我的模拟商店的初始状态传递。

import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import configureMockStore from 'redux-mock-store'; 
import { Provider } from 'react-redux'; store

// Component to be tested
import Register from '../../../src/components/auth/Register';
// Props for the Register component
import { setAlert } from '../../../src/actions/alert';
import { register } from '../../../src/actions/auth';

// Mock Store
const mockStore = configureMockStore();
const store = mockStore({
  setAlert: setAlert,
  register: register,
  authState: {
    isAuthenticated: false,
    loading: false,
  },
});

describe('<Register /> component renders a <section> with className container.', () => {
  let props;

  beforeEach(() => {
    props = {
      setAlert: setAlert,
      register: register,
      authState: {
        token: localStorage.getItem('token'),
        isAuthenticated: false,
        loading: false,
        loggedInUser: null,
      },
    };
  });

  test("Attempt #1: No dive() and .find('section')", () => {
    const wrapper = shallow(
      <Provider store={store}>
        <Register {...props} />
      </Provider>
    );
    const section = wrapper.find('section');
    expect(section.length).toBe(1); // Section.length returns 0
  });

  test("Attempt #2: Use dive() and .find('section')", () => {
    const wrapper = shallow(
      <Provider store={store}>
        <Register {...props} />
      </Provider>
    ).dive();
    const section = wrapper.find('section');
    expect(section.length).toBe(1); // Section.length returns 0
  });

  test("Attempt #3: No dive() and .hasClass('container')", () => {
    const wrapper = shallow(
      <Provider store={store}>
        <Register {...props} />
      </Provider>
    );
    expect(wrapper.hasClass('container')).toEqual(true); // hasClass returns false
  });

  test("Attempt #4: Use dive() and .hasClass('container')", () => {
    const wrapper = shallow(
      <Provider store={store}>
        <Register {...props} />
      </Provider>
    ).dive();
    expect(wrapper.hasClass('container')).toEqual(true); // hasClass returns false
  });

  test('Attempt #5: No dive() and .to.have.lengthOf(1)', () => {
    const wrapper = shallow(
      <Provider store={store}>
        <Register {...props} />
      </Provider>
    );
    expect(wrapper.find('.container')).to.have.lengthOf(1); // Can't read property have of undefined
  });

  test('Attempt #6: Use dive() and .to.have.lengthOf(1)', () => {
    const wrapper = shallow(
      <Provider store={store}>
        <Register {...props} />
      </Provider>
    ).dive();
    expect(wrapper.find('.container')).to.have.lengthOf(1); // Can't read property of undefined
  });
});

以下是wrapper.debug()的输出:

// The wrapper without dive()
<ContextProvider value={{ store: { getState: [Function: getState], getActions: [Function: getActions], dispatch: [Function: dispatch], clearActions: [Function: clearActions], subscribe: [Function: subscribe], replaceReducer: [Function: replaceReducer] }, subscription: { store: { getState: [Function: getState], getActions: [Function: getActions], dispatch: [Function: dispatch], clearActions: [Function: clearActions], subscribe: [Function: subscribe], replaceReducer: [Function: replaceReducer] }, parentSub: undefined, unsubscribe: null, listeners: { notify: [Function: notify] }, handleChangeWrapper: [Function: bound handleChangeWrapper], onStateChange: [Function: notifyNestedSubs] } }}>
      <Connect(Register) setAlert={[Function: setAlert]} register={[Function: register]} authState={{ token: null, isAuthenticated: false, loading: false, loggedInUser: null }} />
    </ContextProvider>

// The Wrapper using dive():
<Connect(Register) setAlert={[Function: setAlert]} register={[Function: register]} authState={{ token: null, isAuthenticated: false, loading: false, loggedInUser: null }} />

我所有的尝试要么返回 0 个节点,要么返回 false。我看的不是正确的水平吗?每次尝试是否都因潜在的缺少 prop 要求而失败,该要求未能尝试呈现组件?

以下是我的 repo 的链接,在分支 test-component-help 下:

My Repo

以及相关文件的位置:

测试文件:./client/__tests__/components/auth/Register.test.js

组件:./client/src/components/auth/Register.js

【问题讨论】:

  • 你能控制台记录你的isAuthenticated吗?也许控制没有到达您的渲染并在重定向时返回。
  • 我在shallow() 调用之后添加了console.log(props.authState.isAuthenticated),它输出false。这是检查isAuthenticated 状态的正确方法吗?
  • 您应该在组件本身而不是在测试文件中添加该日志行。就在您的if(isAuthenticated) 行之前。
  • 我在我的组件中添加了console.log,但是当我运行我的测试文件时,没有输出。这让我相信组件没有被渲染。但是,我在shallow 调用之后检查了wrapper 的长度,它返回1。这是否意味着组件已呈现?如果是这样,为什么console.log 没有被执行?
  • 您的测试脚本test: jest --silent 可能有 --silent 标志?也可以尝试安装并记录包装器调试 API 输出。如果这没有帮助,请在 GitHub 上放置一个指向您项目的链接。如果可以的话,可以看看并做出贡献。

标签: reactjs redux jestjs components enzyme


【解决方案1】:

这可能与您使用shallow 渲染组件这一事实有关,它仅渲染上层组件(没有它是children)因此,您的内在孩子将不存在。

shallow 渲染更改为渲染整个组件的mount

小贴士,您可以致电wrapper.debug() 来查看呈现的包装器的“html”。

【讨论】:

  • 我希望使用 shallow ,因为它提供了真正的单元测试,而不是 mount 会渲染子组件。有没有办法可以进行浅层处理?我已经想出了如何让 wrapper.debug() 工作。我会将结果包含在我的主要帖子中。
【解决方案2】:
  • 不要将authState 作为prop 传递给组件,您需要有正确的模拟存储并且它会作为prop 发送到您的Register 组件。 (而不是你现在正在做的显式道具)。

将您的 mockStore 更新为(或在其范围内为此测试提供单独的模拟):

const store = mockStore({
authState: {
        isAuthenticated: true,
        loading: false,
      }
});

然后,您可以使用这 4 次尝试来运行测试。我想他们会通过的。

【讨论】:

  • 我已经尝试过您的建议,即以各种方式将我的 authState 传递到模拟商店,所有这些都产生了相同的结果。每次尝试模拟商店时,我都没有在渲染时明确传递道具。结果与我将 authState 作为 prop 传递时的结果相同(wrapper.debug() 没有改变,测试仍然导致找到 0 个节点)。我已经更新了我原来的帖子,以反映我嘲笑商店的尝试。
  • 我克隆了你的 repo 并尝试运行测试,但它无法找到一些数据库密钥,而且你的包中也没有提到要使用哪个节点版本。做不了什么。
【解决方案3】:

通过将 Redux 逻辑方面与组件方面分开,我能够在我的 Register 组件中找到 .container className。 Redux 逻辑在单独的操作测试文件和 reducer 测试文件中进行测试,允许我在没有 Redux 逻辑的情况下测试组件的渲染。

为了在没有 Redux 逻辑的情况下测试组件,我必须导出一个未连接到 Redux 存储的组件版本:

/// *** Export the non-connected version of the component for testing.
export const Register = ({ setAlert, register, authState: { isAuthenticated } }) => {
  //...

  if (isAuthenticated) {
    return <Redirect to='/dashboard' />;
  }

  return (
    <section className='container'> <====== I want to verify this container so I can do further testing by calling find('input') on the container.
      <h1 className='large text-primary'>Sign up</h1>
      <p className='lead'>
        <i className='fas fa-user-circle'> Create Your Account</i>
      </p>
      <form onSubmit={handleSubmit} className='form'>
        <div className='form-group'>
          <input
            type='text'
            placeholder='Name'
            value={name}
            onChange={e => handleNameChange(e)}
            required
          />
        </div>
       //...
    </section>

Register.propTypes = {
  setAlert: PropTypes.func.isRequired,
  register: PropTypes.func.isRequired,
  authState: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  authState: state.auth 
});

export default connect(mapStateToProps, { setAlert, register })(Register);

现在我有一个没有 Redux 逻辑的组件版本,我可以导入它并测试它:

import React from 'react';
import { shallow } from 'enzyme';

// *** Import the non-connected component for testing.
import { Register } from '../../../src/components/auth/Register';

// Globals for test
let props;
let wrapper;
let mockSetAlert = jest.fn();
let mockRegister = jest.fn();

// *** No longer need to mock a store to connect to.

describe('<Register /> component.', () => {
  beforeEach(() => {
    props = {
      setAlert: mockSetAlert,
      register: mockRegister,
      authState: {
        isAuthenticated: false,
      },
    };

    // *** No longer need to use a Provider to connect the component to the Redux store
    wrapper = shallow(<Register {...props} />);
  });

  test('Renders a <section> with className container.', () => {
    const section = wrapper.find('section');
    expect(section.length).toBe(1); 
  });

不需要连接到 Redux 存储的 Provider,层次结构很简单。没有 Redux store,也不会混淆是通过 store 传递 props,还是在渲染时直接传递 props。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-06
    • 2020-05-10
    • 1970-01-01
    • 2019-05-15
    • 2019-09-20
    • 2017-04-13
    • 2021-06-24
    • 1970-01-01
    相关资源
    最近更新 更多