在 JEST + ENZYME 中无法达到 100% 的分支覆盖率

Posted

技术标签:

【中文标题】在 JEST + ENZYME 中无法达到 100% 的分支覆盖率【英文标题】:Unable to reach 100% branch coverage in JEST + ENZYME 【发布时间】:2018-01-15 07:28:03 【问题描述】:

我的 LoadingIndicator 测试文件中有以下代码。我正在使用 JEST + Enzyme 来测试 LoadingIndicator 容器类。

我一直在努力将分支覆盖率提高到 100%。它卡在 91% 上。我无法涵盖的分支是 LoadingIndicator 容器类中的 if (!this.timeoutID)。请帮助我了解我在这里缺少什么。

import ...;

import ConnectedLoadingIndicator, LoadingIndicator  from './loadingIndicator';

jest.useFakeTimers();

describe('LoadingIndicator unconnected container component', () => 

  let wrapper;
  let instance;
  beforeEach(() => 

                    wrapper = shallow(<LoadingIndicator
                                        loading='true'
                                        error='false' />);

                    instance = wrapper.instance();
                  );

  it('checks loadingIndicator is hidden on loading false', () => 
    wrapper.setProps( loading: false, error: false );
    expect(instance.state.showIndicator).toBe(false);
    expect(wrapper.find('.loading-hidden').length).toEqual(1);
  );

  it('checks loadingIndicator is shown after timer runs', () => 
    wrapper.setProps( loading: true, error: false );
    jest.runOnlyPendingTimers();
    expect(instance.state.showIndicator).toBe(true);
    expect(wrapper.find('.loading-show').length).toEqual(1);
  );

  it('checks loadingIndicator is hidden on error true', () => 
    wrapper.setProps( loading: true, error: true );
    expect(instance.state.showIndicator).toBe(false);
    expect(wrapper.find('.loading-hidden').length).toEqual(1);
  );

  it('checks destroyTimer behavior on loading false and error false', () => 
    wrapper.setProps( loading: false, error: false );
    expect(instance.timeoutID).toBe(null);
    expect(instance.state.showIndicator).toBe(false);
  );

);

LoadingIndicator 容器类

import ...;

import LoadingIndicatorComponent from '../../../../components/loadingIndicator';

export class LoadingIndicator extends Component 

  constructor(props) 
    super(props);
    this.timeoutID = null;
    this.state = 
      showIndicator: false,
    ;
  

  componentDidMount() 
    this.ensureTimer(this.props);
  

  componentWillUnmount() 
    this.destroyTimer();
  

  componentWillReceiveProps(props) 
    if (props.loading !== this.props.loading
          || props.error !== this.props.error) 
      this.ensureTimer(props);
    
  

  ensureTimer(props) 
    if (props.loading && !props.error) 
      if (!this.timeoutID) 
        this.timeoutID = setTimeout(() => 
          this.timeoutID = null;
          this.setState( showIndicator: true );
        , props.timeoutPeriod);
      
     else 
      this.destroyTimer();
    
  

  destroyTimer() 
    clearTimeout(this.timeoutID);
    this.timeoutID = null;
    this.setState( showIndicator: false );
  

  render() 
    console.log(this.state.showIndicator);
    return (
      <div className =
        `$this.state.showIndicator ? 'loading-show' : 'loading-hidden'`>
        <LoadingIndicatorComponent>
          Loading...
        </LoadingIndicatorComponent>
      </div>
    );
  



const mapStateToProps = (state) => (
  loading: isLoading(state),
  error: hasError(state),
);

// timeoutPeriod of 1000 is for showing loading indicator after 1000ms
LoadingIndicator.defaultProps = 
  timeoutPeriod: 1000,
;

export default connect(mapStateToProps)(LoadingIndicator);

【问题讨论】:

【参考方案1】:

我已经更新了我的测试文件,如下所示。我发现两个分支都必须在一个测试中覆盖,而不是在单独的测试中。如果已经设置解决了覆盖问题,则检查 timeoutID 保持不变。

import ...;

import ConnectedLoadingIndicator, LoadingIndicator  from './loadingIndicator';

jest.useFakeTimers();

describe('LoadingIndicator unconnected container component', () => 

  let wrapper;
  let spy;
  let spyEnsureTimer;
  let instance;
  let props = 
    pendingRequest: 0
  
  beforeEach(() => 
                    wrapper = shallow(<LoadingIndicator ...props />,
                                         lifecycleExperimental: true, );

                    instance = wrapper.instance();
                    spy = jest.spyOn(instance, 'componentWillReceiveProps');
                    spyEnsureTimer = jest.spyOn(instance, 'ensureTimer');
                  );

  it('renders', () => 
    expect(wrapper.length).toEqual(1);
  );

  it('checks for timeoutID remains unchanged if already set', () => 
      instance.ensureTimer(props);
      expect(instance.timeoutID).toBe(0);
      instance.ensureTimer( pendingRequest: 1 );
      expect(instance.timeoutID).toBe(1);
      instance.ensureTimer( pendingRequest: 1 );
      expect(instance.timeoutID).toBe(1);
  );

  it('checks componentWillReceiveProps and ensureTimer is called', () => 
    expect(spy).not.toHaveBeenCalled();
    wrapper.setProps( pendingRequest: 1 );
    expect(spy).toHaveBeenCalled();
    expect(spyEnsureTimer.mock.calls.length).toBe(1);
  );

  it('checks ensureTimer is not called with same props', () => 
    expect(spyEnsureTimer.mock.calls.length).toBe(0);
    wrapper.setProps( pendingRequest: 0 );
    expect(spyEnsureTimer.mock.calls.length).toBe(0);
  );

  it('checks loadingIndicator is shown on pendingRequest more than 0', () => 
    wrapper.setProps( pendingRequest: 1 );
    jest.runOnlyPendingTimers();
    expect(instance.state.showIndicator).toBe(true);
    expect(wrapper.find('.loading-show').length).toEqual(1);
  );

  it('checks destroyTimer behavior on pendingRequest less than 1', () => 
    wrapper.setProps( pendingRequest: 1 );
    jest.runOnlyPendingTimers();
    expect(instance.state.showIndicator).toBe(true);
    wrapper.setProps( pendingRequest: 0 );
    expect(instance.timeoutID).toBe(0);
    expect(instance.state.showIndicator).toBe(false);
    expect(wrapper.find('.loading-hidden').length).toEqual(1);
  );

);

我已经修改了 LoadingIndicator 容器类以及以下内容:

import React,  Component  from 'react';
import  connect  from 'react-redux';

import  selectPendingRequest  from './selectors';
import LoadingIndicatorComponent from '../../../../components/loadingIndicator';
import './loadingIndicator.css';

export class LoadingIndicator extends Component 

  constructor(props) 
    super(props);
    this.timeoutID = 0;
    this.state = 
      showIndicator: false,
    ;
  

  componentDidMount() 
    this.ensureTimer(this.props);
  

  componentWillUnmount() 
    this.destroyTimer();
  

  componentWillReceiveProps(props) 
    if (props.pendingRequest !== this.props.pendingRequest) 
      this.ensureTimer(props);
    
  

  ensureTimer(props) 
    if (props.pendingRequest > 0) 
      if (this.timeoutID === 0) 
        this.timeoutID = setTimeout(() => 
          this.timeoutID = 0;
          this.setState( showIndicator: true );
        , props.timeoutPeriod);
      
     else 
      this.destroyTimer();
    
  

  destroyTimer() 
    clearTimeout(this.timeoutID);
    this.timeoutID = 0;
    this.setState( showIndicator: false );
  

  render() 
    return (
      <div className =
        `$this.state.showIndicator ? 'loading-show' : 'loading-hidden'`>
        <LoadingIndicatorComponent>
          Loading...
        </LoadingIndicatorComponent>
      </div>
    );
  



const mapStateToProps = (state) => (
  pendingRequest: selectPendingRequest(state) //count of pendingRequest
);

LoadingIndicator.defaultProps = 
  timeoutPeriod: 1000,
;

export default connect(mapStateToProps)(LoadingIndicator);

【讨论】:

以上是关于在 JEST + ENZYME 中无法达到 100% 的分支覆盖率的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Jest - Enzyme 测试 React 中 mapStateToProps 中的方法

如何使用 Jest/Enzyme 在功能性 React 组件中测试 lambda 函数?

TypeError:无法读取未定义的属性“viewManagersNames” - 将 Jest 和 Enzyme 与 React Native TypeScript 包集成

Jest 无法运行 .tsx 文件(CRA 2,酶)

React/Jest/Enzyme - 在 .toHaveBeenCalledWith(event) 检查中监视 changeHandler 失败

Jest / Enzyme - 生命周期方法中的模拟异步函数