如何在不失败测试的情况下获得 Jest toThrow 的覆盖

How to get coverage for Jest toThrow without failing test

假设我正在使用 jest --coverage:

测试下面的 React 组件
class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    if (props.invalid) {
      throw new Error('invalid')
    }
  }
}

覆盖率报告会说 throw new Error('invalid') 行未被覆盖。由于 .not.toThrow() 似乎没有涵盖任何内容,因此我使用 Enzyme 创建了以下测试:

const wrapper = shallow(
  <MyComponent invalid />
)

it('should throw', () => {
  function fn() {
    if (wrapper.instance().props.invalid) {
      throw new Error('invalid')
    }
  }
  expect(fn).toThrow()
})

这条线被覆盖了!然而,测试本身失败并显示 encountered a declaration exception - 这意味着原始组件抛出了错误(应该如此)?

我使用toThrow()错了吗?

galki,我认为问题是您在 constructuring 组件时抛出错误。它没有通过测试,正如它应该的那样(你完全正确)。 相反,如果您可以在其他地方提取 prop-checking 函数,在安装过程中不会调用它 - 它会完美运行。例如,我将您的代码段修改为

export default class MyComponent extends React.Component {
  constructor(props) {
    super(props)
  }
  componentWillReceiveProps(nextProps) {
    if (nextProps.invalid) {
      throw new Error('invalid')
    }
  }
  render() {
    return (
      <div/>
    )
  }
}

const wrapper = shallow(
 <MyComponent />
)

it('should throw', () => {
  function fn() {
    wrapper.setProps({invalid: true});
  };
  expect(fn).toThrow();
})

因此,如果您有机会在安装时不抛出错误 - 您将能够对其进行测试。

显然这与 how React 16 handles errors 相关。我设法通过用具有 componentDidCatch 方法的父 React 组件包装 MyComponent 来使测试通过。

这使测试通过但影响覆盖率,我不得不将 shallow 更改为 mount。测试结果如下所示:

class ParentComponent extends React.Component {
  componentDidCatch(error) {
    // console.log(error)
  }
  render() {
    return this.props.children
  }
}

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    if (props.invalid) {
      throw new Error('invalid')
    }
  }
}

const wrapper = mount(
  <ParentComponent>
    <MyComponent invalid />
  </ParentComponent>
)

it('should throw', () => {
  function fn() {
    if (wrapper.props().invalid) {
      throw new Error('invalid test')
    }
  }
  expect(fn).toThrow()
})

更新

在意识到问题是 shallowmount 中抛出的错误(在进行测试之前)之后,我将整个事情简化为:

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    if (props.invalid) {
      throw new Error('invalid')
    }
  }
}

it('should throw', () => {
  let error
  try {
    shallow(<MyComponent invalid />)
  } catch (e) {
    error = e
  }
  expect(error).toBeInstanceOf(Error)
})

意识到这是一个老问题,但对于未来的观众,我想我会扩展@galki 的回答。与其使用 try/catch,不如简单地将 shallow/mount 包装在匿名函数中,然后使用 .toThrowError() 代替:

const TestComponent = () => {
    throw new Error('Test error');
}

describe('Test Component', () => {
    it('Throws an error', () => {
        expect(() => shallow(<TestComponent />)).toThrowError();
    });
});

这使您的代码更加简洁,结果相同。