测试有状态的React组件的方法

前段时间,我被要求为一些React组件编写测试,这本来是一项平凡而乏味的任务,但却在某个时刻给了我一个“恍然大悟”的时刻。项目和组件的具体细节并不重要,但关键的细节是,我正在处理的是每天由一个大团队使用的有状态的React组件,因此这些组件经常被重构和更新。

我最初的方法是编写一些简单的测试,比如检查组件是否正确渲染以及某些事件是否适当触发。在这样做的过程中,我将状态直接与我期望的结果进行比较,将组件的代码与我的断言放在一起。当然,这并不是什么坏事,但对于一个有许多移动部分的代码库来说,这并不是一个好主意。让我给你举个例子来说明为什么:

context('组件在折叠状态下初始化', function() {
  let wrapper;
  beforeEach(function(){
    wrapper = mount(<StatefulComponent />);
  });

  it('组件的state.expanded为false', function() {
    expect(wrapper.state('expanded')).to.be.false;
  });
});

在这个测试中,我们检查组件的状态是否expanded等于false。只要这个简单的条件为真,我们的测试就会通过。这是一个非常简单的测试,即使对于完全不熟悉代码库的人来说,也应该很容易理解。

然而,随着时间的推移,组件的实现可能会发生变化。如果我们的状态中的expanded最终意味着不同的东西会发生什么?或者更糟糕的是,如果它在界面上的反映方式不同呢?

接下来是我的“恍然大悟”时刻:

应该始终将应用程序的UI视为组件的props和state的组合结果。

上述声明意味着在测试时,组件的状态可以被视为一个黑盒,一个抽象层,除非绝对必要,否则不应该访问。因此,与其进行上面提到的测试,我们应该做更像这样的测试:

context('组件在折叠状态下初始化', function() {
  let wrapper;
  beforeEach(function(){
    wrapper = mount(<StatefulComponent />);
  });

  it('组件没有扩展的类', function() {
    expect(wrapper.find('div').hasClass('expanded')).to.be.false;
  });
});

我们的测试仍然易于阅读和理解,但从总体上来说,这是一个更好的测试。

通过直接检查DOM而不是组件的状态,我们向未来的代码作者提供了关于组件输出的信息,而不是要求他们保持现有的实现不变。这似乎是一种更好的记录组件的方式,如果有人重构UI以改变组件的DOM表示方式,也更容易跟踪未来的更改。