簡體   English   中英

如何在使用 Jest 進行單元測試時對 promise 內部函數進行編碼覆蓋

[英]How to code coverage a promise inside functions while unit testing using Jest

在使用笑話進行單元測試時,如何在 function 中編寫覆蓋代碼並捕獲 promise 的 function ? 請看下面的代碼。

服務.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生成覆蓋率報告后

在此處輸入圖像描述

我們可以看到 promise 成功和錯誤 function 中的代碼沒有作為單元測試的一部分被覆蓋。 所以請幫助解決這個問題。 謝謝。

這是解決方案,文件夾結構:

.
├── 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 覆蓋率報告:

在此處輸入圖像描述

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

你只需要使用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);
  });

然后,您的測試將“等待”,直到 promise 被解決或拒絕,它將在thencatch內運行。 您可以在此處了解有關 Jest 如何管理異步代碼的更多信息。

另外,如果你要處理很多承諾,我建議你看看等待期待的package。 它真的可以幫助您編寫測試代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM