【问题标题】:How to test function in private scope?如何在私有范围内测试功能?
【发布时间】:2020-12-04 17:00:01
【问题描述】:

当我尝试直接测试它时,我无法断言函数selectXHandler。因为它是在私有范围内定义的。我无法模拟或监视此功能,因为您无法访问它

export const CheckboxWrapper = ({
  x,
  y,
  z,
  a = [],
}: CheckboxWrapperProps) => {
  const State = useContext(Context);
  const [state, dispatch] = State;
  const { Items } = state;

  const selectXHandler = () => {
    const payload = {
      x,
      a,
    };
    dispatch({ type: action, payload });
  };

  if (y) {
    return (
      <div className="mt5">
        <Checkbox
          checked={selectedItems[x] !== undefined}
          label={z}
          onChange={selectXHandler}
        />
      </div>
    );
  }
  return null;
};
    const selectXHandler = jest.fn();
    await fireEvent.click(checkbox);
    expect(checkbox).toBeChecked();

    expect(selectXHandler).toHaveBeenCalledTimes(1);

我收到以下错误:

    expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

【问题讨论】:

  • 您应该测试面向公众的行为,而不是内部实现细节。例如。测试你的 UI 元素如何变化,而不是调用什么内部函数
  • 我正在研究集成测试,检查功能是否被触发是其中的一部分。 @bsapaka
  • 您可以改为模拟 redux 并检查它的调度是否已被调用以及使用什么参数。它也是你的组件“公共接口”,不像里面的一些私人功能。假设,您重命名了您的内部函数 - 应用程序可能因此而损坏吗?不。另一方面,如果您的组件开始调度操作“A”而不是“B”,它可能会破坏您的应用程序。所以用测试覆盖它是有意义的。
  • @contextq 无论是哪种测试,它都应该始终测试公共合约,而不是内部实现。您应该能够以满足正确行为的方式更改任何内部结构,并且测试应该仍然通过。
  • @skyboyer 我没有使用 redux,而是使用 usereducer 和 context 来创建类似的东西。你能用伪代码提出建议吗?

标签: reactjs typescript jestjs react-testing-library


【解决方案1】:

正如上面在 cmets 中所讨论的,测试一些内部结构是一个糟糕的举动。想象一下这是可能的,我们重命名了该内部函数或将其内联或拆分为两个。我们测试它的测试会失败吗?确实。我们的应用程序会被破坏吗?当然不是。

我们应该绑定到公共接口。对于组件公共接口是:

  1. imports(如输入值)
  2. props(也是输入值)
  3. 渲染结果(输出值)
  4. 如果使用了上下文(也让我们将其视为输入值)

所以我看到了下一个方法:

  1. 对于一些输入道具
  2. 我们模拟点击复选框
  3. 并验证上下文中的dispatch 是否已使用某些所需参数调用。

我不太了解 RTL,所以我的示例将是关于酶的。但我确信将其转换为适当的 RTL 选择器查询会很容易。

import Context from '../where/its/placed/someContext.js'; 


it('dispatch is called with A on checkbox is clicked', () => {
  const dispatch = jest.fn();
  const state = {}; // or some initial state your test requires

  const ourComponent = mount(<Content.Provider value={{ dispatch, state }}>
    <CheckboxWrapper {...somePropsYouNeed} /></Content.Provider>);
  ourComponent.find({label: 'some-label'}).simulate('change');

  expect(dispatch).toHaveBeenCalledWith({ type: 'someType', payload: 'somePayload' });
  expect(dispatch).toHaveBeenCalledTimes(1);
});

【讨论】:

  • 测试一些内部结构很糟糕 - 不是,这取决于具体情况。这对功能组件不利,因为它们不是为此而设计的。我经常看到“测试行为而不是执行”被视为宗教教条,这是 IMO 的一个非常糟糕的举动。 我们测试它的测试会失败吗?确实。我们的应用程序会被破坏吗?当然不是 - 为什么这是一件坏事?比应用程序损坏时不会失败的测试好很多倍。如果你在修复不应该失败的测试上浪费了几个小时,那就太糟糕了。如果断言内部结构可以节省您数小时的调试时间,那很好。 YMMV
  • @EstusFlask 工程总是关于妥协,同意你的看法。但是,如果初学者开发人员从一开始就将自己绑定到测试内部,他们最终会得到两个结果:不可靠的测试和由于脆弱性而浪费时间进行维护。还有一点“为什么这是一件坏事?” - 因为(当然,取决于情况)它使重构几乎不可能。很少有人喜欢痛苦,大多数人只是跳过重构,如果必须更新 10 多个测试以满足提升状态或移动方法到共享助手重构。
  • @EstusFlask 实际上我上面的示例也是一个折衷方案(我意识到这一点)并且部分依赖于内部结构(Context 如何工作的内部结构,针对dispatch 的论点的断言)。完全独立于内部的将是相当集成的,当我们在 Checkbox 上触发事件并验证 另一个 组件是否相应更改时,将呈现 &lt;Provider&gt;&lt;CheckboxWrapper /&gt;&lt;AnotherComponentThatConsumesContext /&gt;&lt;/Provider&gt;。
猜你喜欢
  • 2023-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-28
  • 2019-02-11
  • 1970-01-01
  • 2019-07-04
  • 1970-01-01
相关资源
最近更新 更多