【问题标题】:Testing React Hooks side effects that depends on other side effects (or other tests)测试依赖于其他副作用(或其他测试)的 React Hooks 副作用
【发布时间】:2020-03-19 17:22:16
【问题描述】:

我有一个 React Hook,它有一个简单的输入和按钮,如果没有输入,该按钮将被禁用,并且同一个按钮在启用时执行提取:

function MyComponent() {
  const [ value, setValue ] = useState('')

  function apiRequest() {
    if (!value) {
      return
    }

    axios.get('url')
      .then(console.log)
      .catch(console.log)
  }

  return (
    <div>
      <input onChange={e => setValue(e.target.value)} value={value} />
      <button disabled={!value} onClick={apiRequest}>
        Submit
      </button>
    </div>
  )
}

我用Enzyme 写了两个测试。第一个测试 disabled 属性是否正确,第二个测试它是否实际获取。

it('sets the disabled prop appropriately', function() {
    const wrapper = mount(<MyComponent />)
    const input = wrapper.find('input')
    const btn = wrapper.find('button')
    expect(btn.prop('disabled')).toBeTruthy()
    input.simulate('change', 'abc123')
    expect(btn.prop('disabled')).toBeFalsy()
})

it('fetches on submit', function () {
    const wrapper = mount(<MyComponent />)
    const input = wrapper.find('input')
    const btn = wrapper.find('button')
    input.simulate('change', 'abc123')
    btn.simulate('click')
    expect(axios.get).toHaveBeenCalled()
})

但不幸的是,要进行第二次测试,需要启用按钮,因此必须先输入文本。所以实际上,第二个测试也是无意中测试 disabled 属性,因为如果 disabled 属性设置不正确,它将失败(onClick 不会触发)。

我关注了 React 的 recommended approach

test React components without relying on their implementation details

这是react-testing-library的核心原则,所以我纯粹是在测试副作用。我正在使用酶而不是那个,因为我的团队目前正在使用酶

我如何能够重写我的第二个测试,以便我可以测试获取?提前致谢。

编辑:或者更确切地说,有几种方法可以重写它以正确测试提取?

【问题讨论】:

    标签: reactjs testing jestjs react-hooks enzyme


    【解决方案1】:

    您可以做的一件事是将&lt;div&gt; 替换为&lt;form&gt; 并将onSubmit={e =&gt; apiRequest(value)} 添加到其中,这样按钮可以保持禁用状态,您仍然可以继续进行测试而不会引入不必要的外部因素。

    另外,将您的function apiRequest() {...} 移出组件。它可以将value 作为参数而不是依赖于周围的范围。

    // You could even export this separately and make a test exclusively for this
    // without also having to test the form itself
    function apiRequest ( value ) {
        if (!value) {
          return
        }
    
        axios.get('url')
          .then(console.log)
          .catch(console.log)
    }
    
    function MyComponent() {
      const [ value, setValue ] = useState('')
    
      return (
        <form onSubmit={e => { e.preventDefault(); apiRequest(value); }}>
            <input onChange={e => setValue(e.target.value)} value={value} />
            <button disabled={!value}>
                Submit
            </button>
        </form>
      )
    }
    

    【讨论】:

    • 这个表格非常聪明!但是,如果输入和按钮不是同级的(例如,在模态中 - 模态主体中的表单和模态页脚中的表单 - 每个不同的容器)怎么办?如果我所做的只是将单个按钮包装在一个表单中,这种方法是不合适的吗? (也是我将函数留在里面的原因是因为在我的完整应用程序中,它也会更新状态)
    • 哦,我只是在解决问题中的代码。根据情况,可能有很多不同的方法来处理这个问题。听起来您正在寻找比此示例更确切的解决方案更概念化的解决方案。我建议查看codementor - 我什至不再因为分享这个而获得推荐奖金:(
    • 哦,是的,这是我最小的可重现示例 - 但碰巧您提供的解决方案(完全有效的解决方案)让我后悔将我的示例 too 最小化哈哈,因为什么我描述的实际上是我实际代码中的情况。不过谢谢。
    【解决方案2】:

    我相信你肯定是在测试行为,而不是实现细节。

    如果您说是setState(),它将依赖于实现细节(确保它不适用于功能组件,这就是为什么它是一个糟糕的模式)。

    使用 RTL,您仍然需要在单击按钮之前更改输入,这里没有区别。

    唯一的事情是它依赖于axios 本身的实现细节。您可以使用nock 来处理对任何库发出的任何请求。但我不确定这是否值得。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 1970-01-01
      • 2020-05-08
      • 1970-01-01
      • 2018-03-31
      • 2011-05-23
      • 1970-01-01
      相关资源
      最近更新 更多