[使用Jest进行单元测试时如何在功能内覆盖Promise的代码

问题描述 投票:0回答:2

然后在使用jest进行单元测试时如何编写代码覆盖并在函数内部捕获一个承诺函数?请参见下面的代码。

Service.js

export const userLogin = data => {
  return AjaxService.post(
    "http://localhost/3000/signin", data
  ).then(
    res => {
      return res.data;
    },
    error => {
      return error.response.data;
    }
  );
};

AjaxService.js

export const AjaxService = {
  post: (url, data, headers) => {
    return axios({
      method: "POST",
      url: url,
      headers: headers || { "content-type": "application/json" },
      data: data
    });
  }

}

Example.js

class Login extends Component {

  handleSubmit = (event) => {
    if (this.props.handleSubmit) this.props.handleSubmit(); 
    this.setState({isLoggedIn: true})
    userLogin().then((res) => {
     // when promise resolve
     var response = res;
    }, (err) => {
      // when promise reject  
      var error = err;
    })
  }
  render() {
   return (
    <form id="login-form" onSubmit={(e) => this.handleSubmit(e)} >
     <input type="username" />
     <input type="password" />
     <button type="submit">Login</button>
    </form>
   )
  }

}

Example.test.js

it("test login form submit ", () => {
    wrapper = shallow(<Login />);
    let instance = wrapper.instance(); // get class instance
    instance.handleSubmit(); // will trigger component method
    let actualVal = wrapper.state().isLoggedIn; // get state key value
    expect(true).to.eql(actualVal);
  });

在使用Jest中的--coverage生成覆盖率报告之后

enter image description here

我们可以看到,内部的代码承诺成功和错误功能不会作为单元测试的一部分被覆盖。因此,请帮助解决此问题。谢谢。

javascript unit-testing asynchronous jestjs code-coverage
2个回答
0
投票

这里是解决方案,文件夹结构:

.
├── ajaxService.ts
├── example.spec.tsx
├── example.tsx
└── service.ts

ajaxService.ts

import axios from 'axios';

export const AjaxService = {
  post: (url, data, headers?) => {
    return axios({
      method: 'POST',
      url,
      headers: headers || { 'content-type': 'application/json' },
      data
    });
  }
};

service.ts

import { AjaxService } from './ajaxService';

export const userLogin = data => {
  return AjaxService.post('http://localhost/3000/signin', data).then(
    res => {
      return res.data;
    },
    error => {
      return error.response.data;
    }
  );
};

example.tsx

import React, { Component } from 'react';
import { userLogin } from './service';

export interface ILoginProps {
  handleSubmit(): void;
}

interface ILoginState {
  isLoggedIn: boolean;
}

export class Login extends Component<ILoginProps, ILoginState> {
  constructor(props: ILoginProps) {
    super(props);
    this.state = {
      isLoggedIn: false
    };
  }
  public handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    if (this.props.handleSubmit) {
      this.props.handleSubmit();
    }
    this.setState({ isLoggedIn: true });

    const data = {};
    return userLogin(data).then(
      res => {
        console.log(res);
      },
      err => {
        console.error(err);
      }
    );
  }
  public render() {
    return (
      <form id="login-form" onSubmit={e => this.handleSubmit(e)}>
        <input type="username" />
        <input type="password" />
        <button type="submit">Login</button>
      </form>
    );
  }
}

example.spec.tsx

import React from 'react';
import { shallow } from 'enzyme';
import { Login, ILoginProps } from './example';
import * as service from './service';

describe('Login', () => {
  afterEach(() => {
    jest.restoreAllMocks();
    jest.resetAllMocks();
  });
  const mockedProps: ILoginProps = { handleSubmit: jest.fn() };
  const mockedFormEvent = { preventDefault: jest.fn() };
  const mockedUserLoginResponse = 'mocked data';
  const mockedUserLoginError = new Error('database error');

  it('test login form submit - 1', done => {
    const userLoginSpy = jest.spyOn(service, 'userLogin').mockResolvedValueOnce(mockedUserLoginResponse);
    const logSpy = jest.spyOn(console, 'log');
    const wrapper = shallow(<Login {...mockedProps}></Login>);
    wrapper.find('form').simulate('submit', mockedFormEvent);

    setImmediate(() => {
      expect(mockedFormEvent.preventDefault).toBeCalledTimes(1);
      expect(mockedProps.handleSubmit).toBeCalledTimes(1);
      expect(wrapper.state('isLoggedIn')).toBeTruthy();
      expect(userLoginSpy).toBeCalledWith({});
      expect(logSpy).toBeCalledWith(mockedUserLoginResponse);
      done();
    });
  });

  it('test login form submit - 2', async () => {
    const userLoginSpy = jest.spyOn(service, 'userLogin').mockResolvedValueOnce(mockedUserLoginResponse);
    const logSpy = jest.spyOn(console, 'log');
    const wrapper = shallow(<Login {...mockedProps}></Login>);
    await (wrapper.instance() as any).handleSubmit(mockedFormEvent);
    expect(mockedFormEvent.preventDefault).toBeCalledTimes(1);
    expect(mockedProps.handleSubmit).toBeCalledTimes(1);
    expect(wrapper.state('isLoggedIn')).toBeTruthy();
    expect(userLoginSpy).toBeCalledWith({});
    expect(logSpy).toBeCalledWith(mockedUserLoginResponse);
  });

  it('test login error - 1', done => {
    const userLoginSpy = jest.spyOn(service, 'userLogin').mockRejectedValueOnce(mockedUserLoginError);
    const errorLogSpy = jest.spyOn(console, 'error');
    const wrapper = shallow(<Login {...mockedProps}></Login>);
    wrapper.find('form').simulate('submit', mockedFormEvent);

    setImmediate(() => {
      expect(mockedFormEvent.preventDefault).toBeCalledTimes(1);
      expect(mockedProps.handleSubmit).toBeCalledTimes(1);
      expect(wrapper.state('isLoggedIn')).toBeTruthy();
      expect(userLoginSpy).toBeCalledWith({});
      expect(errorLogSpy).toBeCalledWith(mockedUserLoginError);
      done();
    });
  });
});

带有覆盖率报告的单元测试结果:

 PASS  src/stackoverflow/58110463/example.spec.tsx
  Login
    ✓ test login form submit - 1 (16ms)
    ✓ test login form submit - 2 (3ms)
    ✓ test login error - 1 (10ms)

  console.log node_modules/jest-mock/build/index.js:860
    mocked data

  console.log node_modules/jest-mock/build/index.js:860
    mocked data

  console.error node_modules/jest-mock/build/index.js:860
    Error: database error
        at Suite.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58110463/example.spec.tsx:14:32)
        at addSpecsToSuite (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmine/Env.js:496:51)
        at Env.describe (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmine/Env.js:466:11)
        at describe (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmine/jasmineLight.js:81:18)
        at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58110463/example.spec.tsx:6:1)
        at Runtime._execModule (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runtime/build/index.js:888:13)
        at Runtime._loadModule (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runtime/build/index.js:577:12)
        at Runtime.requireModule (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runtime/build/index.js:433:10)
        at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:201:13
        at Generator.next (<anonymous>)
        at asyncGeneratorStep (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:27:24)
        at _next (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:47:9)
        at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:52:7
        at new Promise (<anonymous>)
        at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:44:12
        at _jasmine (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:206:19)
        at jasmine2 (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:60:19)
        at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runner/build/runTest.js:385:24
        at Generator.next (<anonymous>)
        at asyncGeneratorStep (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runner/build/runTest.js:161:24)
        at _next (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runner/build/runTest.js:181:9)
        at process._tickCallback (internal/process/next_tick.js:68:7)

----------------|----------|----------|----------|----------|-------------------|
File            |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files       |    86.21 |       50 |    63.64 |    84.62 |                   |
 ajaxService.ts |    66.67 |        0 |        0 |    66.67 |                 5 |
 example.tsx    |      100 |       75 |      100 |      100 |                21 |
 service.ts     |       40 |      100 |        0 |       40 |             4,6,9 |
----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        3.035s, estimated 6s

HTML覆盖率报告:

enter image description here

源代码:https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58110463


0
投票

您只需要使用async await sintax:

it("test login form submit ", async () => {
    wrapper = shallow(<Login />);
    let instance = wrapper.instance(); // get class instance
    await instance.handleSubmit(); // will trigger component method
    let actualVal = wrapper.state().isLoggedIn; // get state key value
    expect(true).to.eql(isLoggedIn);
  });

然后,您的测试将“等待”,直到承诺被兑现或被拒绝,它将在thencatch内部运行。您可以了解有关Jest如何管理异步代码here的更多信息。

[此外,如果您有很多承诺,我建议您看一下wait-for-expect软件包。它确实可以帮助您编写测试代码。

© www.soinside.com 2019 - 2024. All rights reserved.