首页 > 解决方案 > 如何测试在功能组件中定义的方法,该方法与 DOM 元素交互并且没有参数

问题描述

我一直无法在我的一个按钮(一个 React 功能组件)上获得 100% 的测试覆盖率。基本上,当它被单击时,它会执行一些代码,然后还会从这个onClick名为resetButtons. 此方法将在应用程序中找到所有类似的按钮并删除一个类。这是一种先发制人的行为,因此一次只能有一个按钮active

到目前为止,我已经测试了点击使用.simulate,传入了一个模拟的domElement. 然后测试该domElement.classList.add方法是否被调用'active'

显然这是一个以 DOM 为中心的操作,我发现测试resetButtons组件中的方法非常困难。特别是考虑到它没有任何方法。

我尝试resetButtons在组件外部定义方法,然后将其导出,以便开玩笑测试可以导入它。但是我一直无法测试该方法,因为它似乎希望它是一个间谍或模拟,而不是方法本身。( Matcher error: received value must be a mock or spy function )

这是反应功能组件:

import React from 'react';
import PropTypes from 'prop-types';
import classes from './MainButton.module.scss';

const MainButton = (props) => {
  const resetButtons = () => {
    const elements = document.getElementsByClassName('mainButton');
    for (let i = 0; i < elements.length; i += 1) {
      elements[i].classList.remove('active');
    }
  };

  const handleClick = (event) => {
    if (!event.target.classList.contains('active')) {
      resetButtons();
      event.target.classList.add('active');
      props.setVisualState(props.className.split('-')[0]);
    }
  };

  return (
    <button
      onClick={handleClick}
      type="button"
      className={`${classes.mainButton} ${props.className}`}
    >
      {props.children}
    </button>
  );
};

MainButton.propTypes = {
  children: PropTypes.node,
  className: PropTypes.string,
  setVisualState: PropTypes.func.isRequired,
};

MainButton.defaultProps = {
  children: 'Button',
  className: '',
};

export default MainButton;

这是测试

import React from 'react';
import { shallow } from 'enzyme';
import MainButton from './MainButton';

describe('MainButton', () => {
  const domElement = { classList: { contains: jest.fn(), remove: jest.fn(), add: jest.fn() } };
  const setVisualStateMock = jest.fn();
  const mainButton = shallow(<MainButton setVisualState={setVisualStateMock} />);

  it(' is rendered properly', () => {
    expect(mainButton).toMatchSnapshot();
  });

  describe('when clicked', () => {
    beforeEach(() => {
      mainButton.find('button').simulate('click', { target: domElement });
    });

    it('it runs `classlist.add` to assign `active` class', () => {
      expect(domElement.classList.add).toHaveBeenCalledWith('active');
    });

    it('it runs set visual state to update `Allergen` container `state`', () => {
      expect(setVisualStateMock).toHaveBeenCalled();
    });
  });
});

目前覆盖率报告报告了 92% 的覆盖率,但分支为 50,导致问题的线路在第 9 行(elements[i].classList.remove('active');line.

我知道在 90% 的情况下,我可能应该继续前进,但这是我想要弄清楚的事情。感觉像解决这个问题会让我得到更好的测试。

希望大家能帮忙!

标签: reactjsjestjsenzyme

解决方案


自己在 DOM 中摸索是一种反模式。这就是 React 的工作。与其一起操作 dom,不如使用target.classList.addstate 属性来保存您的输入当前处于活动状态的状态。然后,在渲染时你可以说className={isActiveInput ? "active": null}.

因为状态不是特定于您的 MainButton 组件,所以您将提升状态。如果您在父级中的某处有状态,则不必通过类名粗略地搜索 DOM 元素并自己操作 dom。

简单地说,React 的规则是:你定义事物应该是什么样子,React 会注意你的定义在 dom 中成为现实。如果你自己操作 DOM - 你做错了。

当所有这些都完成后,你的测试就完全没有问题了,因为你所要做的就是提供适当的状态和道具,这很容易,并检查你的回调是否被触发 onClick。

编辑:高级版本将使用Context,但我会先使用状态提升。


推荐阅读