How to mock http methods in Jest that has private methods - javascript

I am struggling with how to mock below functionality. I need to mock both methods: getAllBookInCategory, deleteBookInCategory
The public method calls private methods and I am assuming I don't have to test private method, only calling public methods and verifying private methods get called. Is it correct?
public methods: getAllBookInCategory, deleteBookInCategory
private method: makeRequest
import rp from "request-promise-native";
export class BookService {
#param bookCategory id of book category
#param boooks list of books
public static async getAllBookInCategory(bookCategory: string) {
try {
const neededInfo = {
url: `https://${process.env.BOOK_HOST}/bookapi/${process.env.BOOKAPI_VERSION}/iterative/bookCategory/${ bookCategory }/books/all `,
method: 'GET',
}
const result = await BookService.makeRequest(bookCategory, neededInfo);
return rp(result);
} catch(error) {
Console.log(`Failed to get All Books in given category ${error}`)
}
}
public static async deleteBookInCategory(bookCategory: string, books: string[]) {
try{
const neededInfo = {
url: `https://${process.env.BOOK_HOST}/bookapi/${process.env.BOOKAPI_VERSION}/ iterative /bookCategory/${ bookCategory }/books/bookDelete?books=${books.join()}`,
method: 'DELETE',
}
const result = await BookService.makeRequest(bookCategory, neededInfo);
return rp(result);
} catch(error) {
Console.log(`Failed to delete books from category: ${error}`)
}
}
private static async makeRequest(bookCategory: string, neededInfo: any, bodydata?: any) {
const authValue = await BookService.getAuthValue(bookCategory, neededInfo);
return {
method: neededInfo.method,
url: neededInfo.url,
headers: {
Host: process.env.BOOK_HOST,
Authorization: authValue,
},
body: bodydata,
json: true
};
}
}

Here is the unit test solution for getAllBookInCategory method. You code has an issue, you should use return await rp(result) instead of using return rp(result).
BookService.ts:
import rp from 'request-promise-native';
export class BookService {
public static async getAllBookInCategory(bookCategory: string) {
try {
const neededInfo = {
url: `https://${process.env.BOOK_HOST}/bookapi/${process.env.BOOKAPI_VERSION}/iterative/bookCategory/${bookCategory}/books/all`,
method: 'GET',
};
const result = await BookService.makeRequest(bookCategory, neededInfo);
return await rp(result);
} catch (error) {
console.log(`Failed to get All Books in given category ${error}`);
}
}
public static async deleteBookInCategory(bookCategory: string, books: string[]) {
try {
const neededInfo = {
url: `https://${process.env.BOOK_HOST}/bookapi/${
process.env.BOOKAPI_VERSION
}/iterative/bookCategory/${bookCategory}/books/bookDelete?books=${books.join()}`,
method: 'DELETE',
};
const result = await BookService.makeRequest(bookCategory, neededInfo);
return rp(result);
} catch (error) {
console.log(`Failed to delete books from category: ${error}`);
}
}
private static async makeRequest(bookCategory: string, neededInfo: any, bodydata?: any) {
const authValue = await BookService.getAuthValue(bookCategory, neededInfo);
return {
method: neededInfo.method,
url: neededInfo.url,
headers: {
Host: process.env.BOOK_HOST,
Authorization: authValue,
},
body: bodydata,
json: true,
};
}
private static async getAuthValue(catetory, info) {
return 'authvalue';
}
}
BookService.test.ts:
import { BookService } from './BookService';
import rp from 'request-promise-native';
jest.mock('request-promise-native', () => jest.fn());
describe('BookService', () => {
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
describe('#getAllBookInCategory', () => {
beforeEach(() => {
process.env.BOOK_HOST = 'example.com';
process.env.BOOKAPI_VERSION = 'v1';
});
afterEach(() => {
process.env.BOOK_HOST = '';
process.env.BOOKAPI_VERSION = '';
});
it('should make request correctly', async () => {
const mAuthvalue = 'mocked auth value';
jest.spyOn(BookService as any, 'getAuthValue').mockResolvedValueOnce(mAuthvalue);
const mResponse = 'success';
rp.mockResolvedValueOnce(mResponse);
const actual = await BookService.getAllBookInCategory('programming');
expect(actual).toEqual(mResponse);
expect(rp).toBeCalledWith({
method: 'GET',
url: 'https://example.com/bookapi/v1/iterative/bookCategory/programming/books/all',
headers: {
Host: 'example.com',
Authorization: mAuthvalue,
},
body: undefined,
json: true,
});
});
it('should print an error when make request failed', async () => {
const mAuthvalue = 'mocked auth value';
jest.spyOn(BookService as any, 'getAuthValue').mockResolvedValueOnce(mAuthvalue);
const logSpy = jest.spyOn(console, 'log');
const mError = new Error('Internal server error');
rp.mockRejectedValueOnce(mError);
await BookService.getAllBookInCategory('programming');
expect(rp).toBeCalledWith({
method: 'GET',
url: 'https://example.com/bookapi/v1/iterative/bookCategory/programming/books/all',
headers: {
Host: 'example.com',
Authorization: mAuthvalue,
},
body: undefined,
json: true,
});
expect(logSpy).toBeCalledWith(`Failed to get All Books in given category ${mError}`);
});
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59315311/BookService.test.ts
BookService
#getAllBookInCategory
✓ should make request correctly (9ms)
✓ should print an error when make request failed (8ms)
console.log node_modules/jest-mock/build/index.js:860
Failed to get All Books in given category Error: Internal server error
----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files | 73.91 | 100 | 60 | 72.22 | |
BookService.ts | 73.91 | 100 | 60 | 72.22 | 21,28,30,32,52 |
----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 5.662s, estimated 13s
You can test deleteBookInCategory method in a same way.
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59315311

Related

How to mock 'cross-fetch' GET POST & PUT methods

I have my code as
import fetch from 'cross-fetch'
const headers = {
Authorization: `Bearer abcdEFGH1234`,
Accept: 'application/json',
'Content-Type': 'application/json',
}
const getData = await fetch('https://api_base_url/rest/api/latest/queue/ProjectKey-PlanKey', {
method: 'GET',
headers: headers,
})
// convert the API Call response to Json
const getDataJson = await getData.json()
const putData = await fetch('https://api_base_url/rest/api/latest/queue/ProjectKey-PlanKey/comments', {
method: 'PUT',
headers: headers,
body: {message: "comment"},
})
// convert the API Call response to Json
const putDataJson = await putData.json()
cross-fetch does not seem to have methods directly like fetch.get or fetch.PUT, so the methods have to be passed via options.
The problem I am facing is how to mock these methods during testing with Jest.
I tried as below but it does not work
const mockFetchGet = jest.fn((arg1, arg2) => {
return new Promise((resolve) => {
resolve(true)
})
})
const mockFetchPost = jest.fn((arg1, arg2, arg3) => {
return new Promise((resolve) => {
resolve(true)
})
})
const mockFetchPut = jest.fn((arg1, arg2, arg3) => {
return new Promise((resolve) => {
resolve(true)
})
})
jest.mock('cross-fetch', () => {
return {
fetch: jest.fn().mockImplementation(() => {
return {
get: mockFetchGet,
post: mockFetchPost,
put: mockFetchPut,
}
}),
}
})
I get the error like below. Can anyone help? ANY GUIDANCE IN THIS IS HIGHLY APPRECIATED
TypeError: (0 , cross_fetch_1.default) is not a function
75 | )
76 |
> 77 | const mergePullRequest = await fetch(
| ^
78 | `https://api_base_url/rest/api/latest/queue/ProjectKey-PlanKey/merge`,
79 | {
80 | method: 'POST',

Mock axios request using jest

I tried following:
jest.mock('axios', () => jest.fn(() => Promise.resolve({ data: testData })));
also tried adding __mocks__/axios.ts:
export default {
default: jest.fn(),
request: jest.fn(),
};
but it returns:
TypeError: Cannot read property 'request' of undefined
7 | const CLIENT_ROUTER_REQUIRED_HEADER = { 'Content-Type': 'application/json' };
8 |
> 9 | axiosRetry(axios, { retries: 3 });
| ^
10 |
11 | const responseData = axios({
12 | baseURL: baseUrl ? baseUrl : '',
AxiosService.ts
import axios, { AxiosResponse } from 'axios';
import axiosRetry from 'axios-retry';
export const axiosRequest = (data: object, baseUrl?: string): Object => {
const CLIENT_ROUTER_END_POINT = '/client-router';
const CLIENT_ROUTER_HTTP_METHOD = 'POST';
const CLIENT_ROUTER_REQUIRED_HEADER = { 'Content-Type': 'application/json' };
axiosRetry(axios, { retries: 3 });
const responseData = axios({
baseURL: baseUrl ? baseUrl : '',
url: CLIENT_ROUTER_END_POINT,
method: CLIENT_ROUTER_HTTP_METHOD,
headers: CLIENT_ROUTER_REQUIRED_HEADER,
data: data,
})
.then(function (response: AxiosResponse) {
return response.data;
})
.catch((e) => {
return JSON.stringify(e);
});
return responseData;
};
index.ts
import { axiosRequest } from './AxiosService';
export const retrieveDataFromServer = async (
httpMethod: string,
gatewayPath: string,
requestParameters: object,
baseUrl?: string
): Promise<Object> => {
const data = {
httpMethod: httpMethod,
gatewayPath: gatewayPath,
requestParameters: requestParameters,
};
const responseData = axiosRequest(data, baseUrl);
return responseData;
};
index.test.ts
import { retrieveDataFromServer } from '../src';
describe('Frontend Client Router React Component', () => {
test('Retrieve data from job-search endpoint', async () => {
// The purpose of this test is to show an example on how to use retrieveDataFromServer()
const data = {
query: 'strawberry',
//...other data
};
const testData = {
responseBody:
'["1", "2", "3"]',
responseCode: 200,
};
jest.mock('axios', () => jest.fn(() => Promise.resolve({ data: testData })));
expect(
await retrieveDataFromServer(
'GET',
'/search',
data,
'http://localhost:8881/'
)
).toMatchObject(testData);
});
});
I ended up adding:
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
const mock = new MockAdapter(axios);
test('Retrieve data from autocomplete endpoint', async () => {
const data: AutocompleteData = {
query: 'strawberry',
};
const testData = [
'strawberry',
];
mock.onPost().replyOnce(200, {
testData,
});
await expect(autocomplete(AutocompleteType.where, data)).resolves.toEqual({
testData: testData,
});
}
to my test code.

Mocking two S3 API calls in the same AWS Lambda function using Jest

I'm trying to mock two S3 calls in the same Lambda function but seem to be getting Access Denied errors so this leads me to believe the S3 calls are not being mocked. The syntax I'm currently using works when mocking just one S3 call in the function but the function I'm currently testing has two S3 calls(deleteObject and putObject).
Here is my mock code:
const putObjectMock = jest.fn(() => ({
promise: jest.fn(),
}));
const deleteObjectMock = jest.fn(() => ({
promise: jest.fn(),
})));
jest.mock("aws-sdk", () => ({
S3: jest.fn(() => ({
deleteObject: deleteObjectMock,
putObject: putObjectMock,
})),
}));
And my test:
const newHandler = Handler.handler
const returnValue = await handler ({
queryStringParameters: {
eventListValue: "test",
eventListName: "test2",
body: {newStuff: "stuff goes here", eventList: [] },
});
expect(returnValue).toEqual({
statusCode:200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true,
},
body: undefined
})
});
And the part of the file that has the two S3 calls:
if(event.queryStringParameters.value){
await s3.deleteObject({Bucket: "my-bucket-name", Key: "name-of-object",
}).promise()
}
const putObjectResponse = await s3.putObject({Bucket: "my-bucket-name", Key:
"name-of-object",
ContentType: "application/json", Body: event.body}).promise();
Access denied is returned when I try to test this. Any help would be great.
Here is the unit test solution:
handler.js:
import AWS from 'aws-sdk';
const s3 = new AWS.S3();
export async function handler(event) {
if (event.queryStringParameters.value) {
await s3.deleteObject({ Bucket: 'my-bucket-name', Key: 'name-of-object' }).promise();
}
const putObjectResponse = await s3
.putObject({ Bucket: 'my-bucket-name', Key: 'name-of-object', ContentType: 'application/json', Body: event.body })
.promise();
return putObjectResponse;
}
handler.test.js:
import { handler } from './handler';
import AWSMock from 'aws-sdk';
jest.mock('aws-sdk', () => {
const putObjectOutputMock = {
promise: jest.fn(),
};
const putObjectMock = jest.fn(() => putObjectOutputMock);
const deleteObjectOutputMock = {
promise: jest.fn(),
};
const deleteObjectMock = jest.fn(() => deleteObjectOutputMock);
const mS3 = {
deleteObject: deleteObjectMock,
putObject: putObjectMock,
};
return { S3: jest.fn(() => mS3) };
});
describe('61719699', () => {
it('should delete object and put object', async () => {
const mS3 = new AWSMock.S3();
const mPutObjectResponse = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: undefined,
};
mS3.putObject().promise.mockResolvedValueOnce(mPutObjectResponse);
const mEvent = { queryStringParameters: { value: 'test' }, body: { newStuff: 'stuff goes here', eventList: [] } };
const returnValue = await handler(mEvent);
expect(returnValue).toEqual({
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: undefined,
});
expect(mS3.deleteObject).toBeCalledWith({ Bucket: 'my-bucket-name', Key: 'name-of-object' });
expect(mS3.deleteObject().promise).toBeCalled();
expect(mS3.putObject).toBeCalledWith({
Bucket: 'my-bucket-name',
Key: 'name-of-object',
ContentType: 'application/json',
Body: { newStuff: 'stuff goes here', eventList: [] },
});
});
});
unit test results with coverage report:
PASS stackoverflow/61719699/handler.test.js (10.22s)
61719699
✓ should delete object and put object (7ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 100 | 66.67 | 100 | 100 |
handler.js | 100 | 66.67 | 100 | 100 | 6
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.94s
Problem was because I was using the specific service import instead of the top-level import in my code file.
Changed
const S3 = require("aws-sdk/clients/s3");
to
const AWS = require("aws-sdk");

unit-test with HTTP request returns Promise { <pending> }

I am using axios mock adapter to mock HTTP request to test my function. After I defined the behaviour for the function, and then I created an instance of the class to call the function, the result is
**Promise { <pending> }**,
what is the problem? how can I return the value I defined?
Here is my code:
UserService.js
export default class UserService {
getUserInfo = userId => {
const params = {
userId,
};
return axios
.get('https://www.usefortesting.com', {
params: { userId: params },
})
.then(response => response.data.userInfo)
.catch(error => error);
};
}
UserService.test.js
import React from 'react';
import axios from 'axios';
import UserService from './UserService';
import MockAdapter from 'axios-mock-adapter';
describe('testing', () => {
let axiosMock;
const Info = {
userInfo: {
id: '123',
name: 'omg',
},
};
beforeEach(function() {
axiosMock = new MockAdapter(axios);
});
afterEach(() => {
axiosMock.reset();
axiosMock.restore();
});
it('testing', () => {
axiosMock
.onGet('https://www.usefortesting.com', {
params: { userId: 'user_1' },
})
.reply(200, Info);
let userService = new UserService();
let response = userService.getUserInfo('user_1');
console.log(response);
});
});
You need to await for response in your test. Either use callbacks or async/await as shown below.
Your test should be like this:
it('testing', async () => { // notice async here
axiosMock
.onGet('https://www.usefortesting.com', {
params: { userId: 'user_1' },
})
.reply(200, Info);
let userService = new UserService();
let response = await userService.getUserInfo('user_1'); // notice await here
console.log(response);
});
OR
it('testing', () => {
...
userService.getUserInfo('user_1').then(response => {
console.log(response);
});
});
You can check this link on jest docs for more examples.
Also there is error in your getUserInfo() method, in params you are passing an object for userId but you need to pass string or int. What you should do is:
return axios.get('https://www.usefortesting.com', {
params: { userId: params.userId },
})...
OR
return axios.get('https://www.usefortesting.com', {
params,
})...

apisauce (axios wrapper) - Can i handle the response error to write once and automatically called instead of manually add if else

I have too many API to called in my project and I'm using apisauce to communicating into API
I've an example code :
api.get("myUrl")
.then((response)=>{
console.log(response)
if(response.ok && response.status == 200){
//displaying data to screen
} else{
//display alert failed to call API
}
})
for now I want to handle if the authorization token is failed I want to redirect to login page, but I don't want to add the code authorization token is failed to all of my api request
Is there a way to create code else if(!response.ok && response.status == 401){redirectToLogin()} once instead of add this code into all of my API.get?
For our react-native app I create a class with own get, delete, put, update methods, which handles errors and then invoke apisauce.get e.t.c.
I use flow type annotations, but it'll be nicer using typescript for easily creating private methods.
type ApiRequest = (url: string, payload?: Object) => Promise<ApiResponse>;
export class Api {
apiSauce: {
get: ApiRequest,
post: ApiRequest,
put: ApiRequest,
delete: ApiRequest,
};
constructor(baseURL: string) {
this.apiSauce = apisauce.create({
baseURL,
headers: {
"Cache-Control": "no-cache",
},
timeout: 60 * 1000,
});
}
_get = async (url: string, payload?: Object) => {
const res = await this._handleResponse(this.apiSauce.get, { url, payload });
return res;
};
_put = async (url: string, payload?: Object) => {
const res = await this._handleResponse(this.apiSauce.put, { url, payload });
return res;
};
_post = async (url: string, payload?: Object) => {
const res = await this._handleResponse(this.apiSauce.post, { url, payload });
return res;
};
_delete = async (url: string, payload?: Object) => {
const res = await this._handleResponse(this.apiSauce.delete, { url, payload });
return res;
};
_handleResponse = async (apiRequest: ApiRequest, params: ApiRequestParams): Promise<ApiResponse> => {
const res = await apiRequest(params.url, params.payload);
return this._handleError(res);
};
_handleError = (res: ApiResponse) => {
if (!res.ok) {
if (res.status === 401 || res.status === 403) {
// **redirect to login page**
}
if (res.data && res.data.message) {
showNotification(res.data.message);
}
showNotification(res.problem);
}
return res;
};
getUser = async (userId: string): Promise<User> => {
const response = await this._get(`users/{userId}`);
return transformUserApiResponse(response.data);
};
}
const MyApi = new Api(BASE_URL);

Categories

Resources