用酶测试连接组件

IT技术 reactjs redux react-redux enzyme
2021-05-02 04:57:58

我正在学习参加这个测试课程,通过设置一个store factory测试助手来测试连接的组件,该助手“创建一个与我们商店的配置相匹配的测试商店”。在下面,您可以看到我连接的示例组件以及用于设置测试的代码,其中我为我的示例组件创建了一个连接的浅酶包装器。但是,似乎是我传递给示例组件的初始状态,在这种情况下,在{jotto: 'foo'}创建此浅层包装器时不会传递给我的示例组件。我做错了什么,在运行酶测试时如何正确地重新创建必要的商店配置?谢谢!

示例组件:

import React from 'react';
import {connect} from 'react-redux';

const SampleComponent = (props) => {
  console.log(props);
  return (
    <div>This is a sample component!</div>
  );
};

const mapStateToProps = (state) => ({
  jotto: state.jotto,
});

export default connect(mapStateToProps)(SampleComponent);

减速器:

import * as jottoActionTypes from 'actionTypes/jottoActionTypes';

export const initialState = {
  isSuccess: false,
};

const jotto = (state = initialState, action) => {
  switch (action.type) {
    case jottoActionTypes.CORRECT_GUESS:
      return {
        ...state,
        isSuccess: true,
      };
    default:
      return state;
  }
};

export default jotto;

根减速器:

import {combineReducers} from 'redux';
import {connectRouter} from 'connected-react-router';
import jotto from 'reducers/jottoReducer';

export default (historyObject) => combineReducers({
  jotto,
  router: connectRouter(historyObject),
});

设置测试:

import React from 'react';
import {shallow} from 'enzyme';
import {createStore} from 'redux';
import rootReducer from 'reducers/rootReducer';
import SampleComponent from './sampleComponent';

export const storeFactory = (initialState) => createStore(rootReducer, initialState);

const store = storeFactory({jotto: 'foo'});
const wrapper = shallow(<SampleComponent store={store} />).dive();
console.log(wrapper.debug());

// Result:
      { store:
         { dispatch: [Function: dispatch],
           subscribe: [Function: subscribe],
           getState: [Function: getState],
           replaceReducer: [Function: replaceReducer],
           [Symbol(observable)]: [Function: observable] },
        jotto: undefined,
        dispatch: [Function: dispatch],
        storeSubscription:
         Subscription {
           store:
            { dispatch: [Function: dispatch],
              subscribe: [Function: subscribe],
              getState: [Function: getState],
              replaceReducer: [Function: replaceReducer],
              [Symbol(observable)]: [Function: observable] },
           parentSub: undefined,
           onStateChange: [Function: bound onStateChange],
           unsubscribe: [Function: unsubscribe],
           listeners:
            { clear: [Function: clear],
              notify: [Function: notify],
              get: [Function: get],
              subscribe: [Function: subscribe] } } }
2个回答

只是提一下 Udemy 课程……它不是最好的学习工具。指导员办法使用测试data attributes它们是不必要的jestenzyme测试(它们还拥挤了DOM与未使用的属性)。

此外,她的代码经验大约是初学者水平,她犯了很多错误和奇怪的代码选择。也就是说,学什么,你可以从它,并开始寻找到那些谁维护流行NPM包创建测试(最详细记录和流行的包将包含要教你的一个更实际的方法测试unitintegration测试)。

无论如何,我离题了,您有两种测试 a 的选项container

  1. exportclass/ pure functionshallowmount纸包起来,然后用假props更新(很容易,头痛的更小,更常见的做)
  2. 将您的组件包装在 redux<Provider>和 react-router-dom 中<MemoryRouter>,然后mount它(可能会变得非常复杂,因为它需要半深入的了解:酶以及在安装组件时它如何解释 DOM,redux 的操作/reducer 流程​​,如何创建模拟实现和/或模拟文件,以及如何正确处理promise基于操作的操作)。

工作示例(单击Tests选项卡运行测试;.tests.js在下面提到的目录中找到):

编辑测试 Redux 组件


注意:Codesandbox 目前有一些测试限制,如下所述,因此请根据您的本地项目进行调整。

container/Dashboard/__tests__/UnconnectedDashboard.test.js(你可以很容易地mount包装这个未连接的组件来对其深层嵌套的子节点进行断言)

import { Dashboard } from "../index.js";

/* 
   codesandbox doesn't currently support mocking, so it's making real
   calls to the API; as a result, the lifecycle methods have been
   disabled to prevent this, and that's why I'm manually calling
   componentDidMount.
*/

const getCurrentProfile = jest.fn();

const fakeUser = {
  id: 1,
  name: "Leanne Graham",
  username: "Bret",
  email: "Sincere@april.biz",
  address: {
    street: "Kulas Light",
    suite: "Apt. 556",
    city: "Gwenborough",
    zipcode: "92998-3874",
    geo: {
      lat: "-37.3159",
      lng: "81.1496"
    }
  },
  phone: "1-770-736-8031 x56442",
  website: "hildegard.org",
  company: {
    name: "Romaguera-Crona",
    catchPhrase: "Multi-layered client-server neural-net",
    bs: "harness real-time e-markets"
  }
};

const initialProps = {
  getCurrentProfile,
  currentUser: {},
  isLoading: true
};

describe("Unconnected Dashboard Component", () => {
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(<Dashboard {...initialProps} />);
    wrapper.instance().componentDidMount();
  });

  afterEach(() => wrapper.unmount());

  it("initially renders a spinnner", () => {
    expect(getCurrentProfile).toHaveBeenCalled();
    expect(wrapper.find("Spinner")).toHaveLength(1);
  });

  it("displays the current user", () => {
    wrapper.setProps({ currentUser: fakeUser, isLoading: false });
    expect(getCurrentProfile).toHaveBeenCalled();
    expect(wrapper.find("DisplayUser")).toHaveLength(1);
  });

  it("displays a signup message if no users exist", () => {
    wrapper.setProps({ isLoading: false });
    expect(getCurrentProfile).toHaveBeenCalled();
    expect(wrapper.find("DisplaySignUp")).toHaveLength(1);
  });
});

容器/仪表板/__tests__/ConnectedDashboard.test.js

import Dashboard from "../index";
// import { getCurrentProfile } from "../../../actions/profileActions";
import * as types from "../../../types";

/* 
  codesandbox doesn't currently support mocking, so it's making real
  calls to the API; however, actions like getCurrentProfile, should be
  mocked as shown below -- in your case, you wouldn't need to use
  a promise, but instead just mock the "guessedWord" action and return
  store.dispatch({ ... })
*/

const fakeUser = {
  id: 1,
  name: "Leanne Graham",
  username: "Bret",
  email: "Sincere@april.biz",
  address: {
    street: "Kulas Light",
    suite: "Apt. 556",
    city: "Gwenborough",
    zipcode: "92998-3874",
    geo: {
      lat: "-37.3159",
      lng: "81.1496"
    }
  },
  phone: "1-770-736-8031 x56442",
  website: "hildegard.org",
  company: {
    name: "Romaguera-Crona",
    catchPhrase: "Multi-layered client-server neural-net",
    bs: "harness real-time e-markets"
  }
};

const flushPromises = () => new Promise(resolve => setImmediate(resolve));

describe("Connected Dashboard Component", () => {
  let store;
  let wrapper;
  beforeEach(() => {
    store = createStoreFactory();
    wrapper = mount(
      <Provider store={store}>
        <MemoryRouter>
          <Dashboard />
        </MemoryRouter>
      </Provider>
    );
  });

  afterEach(() => wrapper.unmount());

  it("initially displays a spinner", () => {
    expect(wrapper.find("Spinner")).toHaveLength(1);
  });

  it("displays the current user after a successful API call", async () => {
    /* 
      getCurrentProfile.mockImplementationOnce(() => new Promise(resolve => {
        resolve(
          store.dispatch({
            type: types.SET_SIGNEDIN_USER,
            payload: fakeUser
          })
        );
      });

      await flushPromises();
      wrapper.update();

      expect(wrapper.find("DisplayUser")).toHaveLength(1);
    */

    store.dispatch({
      type: types.SET_SIGNEDIN_USER,
      payload: fakeUser
    });

    wrapper.update();

    expect(wrapper.find("DisplayUser")).toHaveLength(1);
  });

  it("displays a signup message if no users exist", async () => {
    /* 
      getCurrentProfile.mockImplementationOnce(() => new Promise((resolve,reject) => {
        reject(
          store.dispatch({
            type: types.FAILED_SIGNEDIN_USER
          })
        );
      });

      await flushPromises();
      wrapper.update();

      expect(wrapper.find("DisplaySignUp")).toHaveLength(1);
    */

    store.dispatch({
      type: types.FAILED_SIGNEDIN_USER
    });

    wrapper.update();

    expect(wrapper.find("DisplaySignUp")).toHaveLength(1);
  });
});

解决方案:鉴于我使用的是connected-react-router.

import rootReducer from 'reducers/rootReducer';
import {createBrowserHistory} from 'history';


export const storeFactory = (initialState) => createStore(rootReducer(createBrowserHistory()), initialState);