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

时间:2017-08-07 21:51:50

标签: javascript reactjs react-redux jestjs enzyme

我的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 个答案:

答案 0 :(得分: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);