Reactjs 如何测试在功能组件中定义的方法,该方法与DOM元素交互且没有参数

Reactjs 如何测试在功能组件中定义的方法,该方法与DOM元素交互且没有参数,reactjs,jestjs,enzyme,Reactjs,Jestjs,Enzyme,我的一个按钮(一个React功能组件)无法获得100%的测试覆盖率。基本上,当它被单击时,它会执行一些代码,然后还会从这个onClick调用另一个方法resetButtons。此方法将在应用程序中找到所有类似的按钮,并删除一个类。这是一种抢占行为,因此一次只能激活一个按钮 到目前为止,我已经使用.simulate测试了单击,并传入了一个模拟的domeElement。然后测试'active'是否调用了doelement.classList.add方法 显然,这是一个以DOM为中心的操作,我发现很难

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

到目前为止,我已经使用
.simulate
测试了单击,并传入了一个模拟的
domeElement
。然后测试
'active'
是否调用了
doelement.classList.add
方法

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

我尝试在组件外部定义
resetButtons
方法,然后将其导出,以便jest测试可以导入它。然而,我一直无法测试该方法,因为它似乎希望它是一个间谍或模拟,而不是方法本身。(
Matcher错误:收到的值必须是mock或spy函数

以下是react功能组件:

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;

从“React”导入React;
从“道具类型”导入道具类型;
从“/MainButton.module.scss”导入类;
常量主按钮=(道具)=>{
常量重置按钮=()=>{
常量元素=document.getElementsByClassName('mainButton');
for(设i=0;i{
如果(!event.target.classList.contains('active')){
重置按钮();
event.target.classList.add('active');
props.setVisualState(props.className.split('-')[0]);
}
};
返回(
{props.children}
);
};
MainButton.propTypes={
子项:PropTypes.node,
类名:PropTypes.string,
setVisualState:PropTypes.func.isRequired,
};
MainButton.defaultProps={
孩子们:'按钮',
类名:“”,
};
导出默认主按钮;
这是测试

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();
    });
  });
});
从“React”导入React;
从“酶”导入{shall};
从“/MainButton”导入MainButton;
描述('MainButton',()=>{
const-domElement={classList:{contains:jest.fn(),remove:jest.fn(),add:jest.fn()};
const setVisualStateMock=jest.fn();
const mainButton=shallow();
它('正确渲染',()=>{
expect(main按钮).toMatchSnapshot();
});
描述('单击时',()=>{
在每个之前(()=>{
mainButton.find('button').simulate('click',{target:domeElement});
});
它('it runs`classlist.add`to assign`active`class',()=>{
expect(doElement.classList.add).toHaveBeenCalledWith('active');
});
它('它运行设置视觉状态以更新'过敏原'container'state',()=>{
期望(setVisualStateMock).tohaveBeenCall();
});
});
});
当前,覆盖率报告报告覆盖率为92%,但分支为50,导致故障的行位于第9行(元素[i].classList.remove('active');行)

我知道在90%的时候我应该继续前进,但这是我想弄明白的事情。我觉得如果我能了解这一点,我会成为一个更好的测试者


希望你们能帮上忙!

在DOM中摸索是一种反模式。这是React的工作。不要用
target.classList.add操作DOM。你应该有一个state属性,它保存当前活动的输入状态。然后,在呈现时,你可以说
className={isActiveInput?“活动”:空}

因为状态不是特定于MainButton组件的,所以您可以这样做。如果您在父级中的某个位置具有该状态,则不必按类名粗略地搜索DOM元素并自行操作DOM

简单地说,React的规则是:定义事物的外观,React会注意让您的定义在dom中成为现实。如果您自己操纵dom,那么您就做错了

当所有这些都完成后,测试就不会有任何问题,因为您所要做的就是提供适当的状态和道具(这很容易),并检查回调是否在单击时触发


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

您应该能够挂载多个
main按钮,单击其中一个按钮,并期望其他按钮上调用了
doElement.classList.remove

然而,用户konqi是正确的,React提供了更好的操作元素/组件的方法

您可以替换此测试:

expect(domElement.classList.add).toHaveBeenCalledWith('active');

通过一个测试,检查按钮是否具有(或不具有)活动的
类名(而不是检查函数是否使用正确的参数调用).有了这个测试,如果你愿意的话,你可以按照konqi建议的方式重构它。

我同意你解释一切的方式。但我只是想知道你为什么提到上下文?这看起来有点超出了问题的范围。你能稍微触及一下吗?当然,而不是通过潜在的mul提升和传递状态在多个层次的组件中,上下文允许将状态放入上下文中,然后在需要时注入上下文。(无论如何,对于像这样的小事情来说,垃圾上下文可能是个坏主意,提升状态应该足够了)非常感谢konqi,这在我的头脑中非常有意义,并且将是我在将来记住的事情。(如果我不能在不使用更复杂的方法的情况下测试代码,我应该一直探索代码的行为。)关于在react中不使用dom操作的想法听起来很明显,现在你可以大声说出来了。非常好