使用酶和Jest在组件中找到className

时间:2020-09-15 06:15:37

标签: reactjs redux jestjs components enzyme

我正在尝试执行简单的单元测试,以确认我的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。我看的水平不正确吗?每次尝试是否都因潜在的缺少道具要求而失败,而道具要求却未尝试渲染组件?

以下是分支test-component-help下我的仓库的链接:

My Repo

以及问题所在位置的文件:

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

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

3 个答案:

答案 0 :(得分:0)

这可能与以下事实有关:您正在使用shallow渲染组件,该组件仅渲染上层组件(没有children),因此您的内部子对象将不存在。

shallow渲染更改为mount,以渲染整个组件。

一个小技巧,您可以调用wrapper.debug()来查看呈现的包装器的“ html”。

答案 1 :(得分:0)

  • 您需要具有正确的模拟存储,而不是将authState作为组件传递给组件,而是将其作为prop发送到您的Register组件。 (而不是像现在这样使用显式道具)。

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

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

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

答案 2 :(得分:0)

通过将Redux逻辑方面与Component方面分开,我能够在我的Register组件中找到.container className。 Redux逻辑在单独的测试文件中进行了操作测试,在Reduce逻辑中进行了测试文件,使我能够在没有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商店,那么在将道具传递通过商店还是在渲染过程中直接传递道具方面也没有任何困惑。

相关问题