Reputation: 359
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
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
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
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:
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58110463
Upvotes: 2