Suraj A J
Suraj A J

Reputation: 359

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

How to code cover then and catch function of a promise inside a function while unit testing using jest? Please see code below.

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);
  });

After generating coverage report using --coverage in Jest

enter image description here

We can see that the code inside promise success and error function doesn't get covered as part of unit testing. So please help cover this. Thanks.

Upvotes: 1

Views: 2857

Answers (2)

Daniel Mar&#237;n
Daniel Mar&#237;n

Reputation: 1447

You just have to use the 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);
  });

Then, your test will "wait" until the promise is resolved or rejected and it will run inside the then or the catch. You can learn more about how Jest manages the asynchronous code here.

In addition, if you are going to work with a lot of promises, I recommend you to take a look to the wait-for-expect package. It could really help you to code your tests.

Upvotes: 1

Lin Du
Lin Du

Reputation: 102287

Here is the solution, folder structure:

.
├── 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();
    });
  });
});

Unit test result with coverage report:

 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 coverage report:

enter image description here

source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58110463

Upvotes: 2

Related Questions