i am trying to use jest with nextJS to test API. I am using a custom interceptor for all http request to have authorization token on header. Here is my interceptor code
Api.ts
import axios from 'axios';
import config from '../config/index';
const Api = () => {
const defaultOptions = {
baseURL: config.APIENDPOINT,
method: 'get',
headers: {
'Content-Type': 'application/json',
},
};
// Create instance
let instance = axios.create(defaultOptions);
// Set the AUTH token for any request
instance.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
//#ts-ignore
config.headers.Authorization = token ? `${token}` : '';
return config;
});
instance.interceptors.response.use((res) => {
return res
});
return instance;
};
export default Api();
Here is the code to call the API
export const loadMerchants = async (id: any) => {
const data = await Api.get(config.APIENDPOINT + "/merchants/company/" + id)
console.log("data" ,data);
return (data)
}
And here is my test code
const axios = require('axios');
jest.mock('axios', () => {
return {
get: jest.fn(),
create: jest.fn(() => ({
interceptors: {
request: { use: jest.fn(() => Promise.resolve({ data: { foo: 'bar' } })) },
response: { use: jest.fn(() => Promise.resolve({ data: { foo: 'bar' } })) },
}
}))
}
})
it('Merchant API call', async () => {
axios.get.mockResolvedValue({
data: [
{
userId: 1,
id: 1,
title: 'My First Album'
},
{
userId: 1,
id: 2,
title: 'Album: The Sequel'
}
]
});
const merchants = await loadMerchants("1")
console.log(merchants) //always undefined
// expect(merchants).toEqual('some data');
});
on my API call if use axios.get instead of Api.get i get the correct results. I have looked into google and haven`t found any solutions.
Any help would be appreciated. Thank you.
Related
In my app I'm sending a request to my backend app from which I get a response with id like { 'id': '12345'}. I saves this id as loadId inside data, here:
export default {
name: 'SyncProducts',
data() {
return {
loadId: '',
Now I want to send another POST fetchSyncedProductsResultRequest when this data loadId change from empty. How to do so?
Below my code:
imports.js
const createApparelMagicProductsRequest = (self, products) => {
const jwtToken = self.$store.state.idToken;
console.log(products)
console.log()
const payload = JSON.stringify({ product_codes: products['product_codes'].split(',') })
return axios
.post(`/api/v1/imports/products_batches`, payload,{
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(response => response.data['id'])
};
const fetchSyncedProductsResultRequest = (token, id) => {
return axios
.get(`/api/v1/imports/products_batches`, {
params: { id: id },
headers: {
Authorization: `Bearer ${token}`,
}
})
.then(response => {
return response.data['result']
})
};
sync_products.vue
<script>
import {
fetchSyncedProductsResultRequest,
createApparelMagicProductsRequest
} from '../../api/imports'
export default {
name: 'SyncProducts',
data() {
return {
styleCodes: [],
fetchedProductSyncResult: [],
loadId: '',
}
},
async mounted() {
await fetchSyncedProductsResultRequest(this, load.id)
this.syncedProductsFetched = true
this.pageChanged(this.currentPage)
},
async mounted() {
const jwtToken = this.$store.state.idToken;
fetchSyncedProductsResultRequest(jwtToken).then(data => {
this.fetchedProductSyncResult = data
})
},
</script>
Use a watcher on loadId that calls fetchSyncedProductsResultRequest() with the new value if it's changed from an empty string to a non-empty string:
export default {
watch: {
loadId(newValue, oldValue) {
if (!oldValue && newValue) {
const jwtToken = this.$store.state.idToken;
fetchSyncedProductsResultRequest(jwtToken, newValue).then(data => {
this.fetchedProductSyncResult = data
});
}
}
}
}
demo
import {
createClient,
defaultExchanges,dedupExchange, cacheExchange, fetchExchange,
subscriptionExchange,
gql
} from "#urql/core";
import { createClient as createWSClient } from "graphql-ws";
import { pipe, subscribe } from "wonka";
import { getToken, setToken } from "./helper";
const wsClient = createWSClient({
url: 'wss://**********/subscriptions',
reconnect: true,
});
const client = createClient({
url: "https://***********/",
fetchOptions: () => {
const token = getToken()
return token ? { headers: { authorization: `Bearer "${token}"` } } : {}
},
// the default:
exchanges: [
...defaultExchanges,
subscriptionExchange({
forwardSubscription(operation) {
return {
subscribe: (sink) => {
const dispose = wsClient.subscribe(operation, sink);
return {
unsubscribe: dispose,
};
},
};
},
}),
]
});
SUB_TO_MESSAGES = async () => {
console.log('sub')
const token = getToken();
console.log(String(token))
const { unsubscribe } = pipe(
await client.subscription(messageAdded,{ jwt: token }),
subscribe((result) => {
console.log(result)
})
)
};
I dont get the same issue with try and catch using GraphQL-WS but I still dont get any data from the server. The assignment is a vanillaJS project using GraphQL.I didndt post the url, jwt token,or the GET, POST, REgG as they work as intended. The rendering is done with a proxy. The error message is:
Connection Closed: 4500 Cannot read properties of undefined (reading 'Authorization')
Even playground doesnt work. Something wrong with the endpoint. It worked 2 weeks ago but admin says it still work yet I can find the problem. It used to work for me.
Here is the try and catch version:
import { createClient} from "graphql-ws";
import pStore from "./handler.js";
import { getToken } from "./helper";
const client = createClient({
url: "wss://******/subscriptions",
reconnect: true,
connectionParams:{
headers: {
"Authorization":`Bearer ${getToken()}`
}
},
})
async SUB_MESSAGE() {
try {
console.log('called Gql server')
const onNext = (res) => {
let obj = res.data.messageAdded
console.log(obj)
pStore[obj.id] = obj
pStore.render(obj)
};
let unsubscribe = () => {
/* complete the subscription */
};
new Promise((resolve, reject) => {
client.subscribe({
query: `subscription{messageAdded(jwt:"${getToken()}"){id text fromAgent createdAt updatedAt}}`,
},
{
next: (data)=> onNext(data),
error: reject,
complete: () => resolve(true),
})
})
}catch(error){
console.error('There has been a problem with your ws operation:', error);
}
}
Either way I think its a ad character, scope issue but I dont know where.
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.
I need your help with this issue that I am having while unit testing a Got client hook
where I do logging of HTTP requests. I am using Jest.
I am getting an expectations error that is seeing the argument to .toBeCalledWith as object whereas it is a string when I console log it. Maybe I am doing something wrong here. Please let me know.
got-client.js below
const http = require('http');
const https = require('https');
const got = require('got');
const _ = require('lodash');
const { name: packageName, version: packageVersion } = require('../../package.json');
const keepAliveOptions = { keepAlive: true, keepAliveMsecs: 20000 };
let clients = {};
const allowedHeaders = ['user-agent', 'x-forwarded-for', 'referer', 'content-length'];
const filterHeaders = headers => _.pick(headers, allowedHeaders);
const gotLoggingHooks = (name, logger) => ({
hooks: {
beforeRequest: [
options => {
const { url, method, headers } = options;
logger.debug({
message: `${name} request ${options.method} ${options.url}`,
http_request: {
method,
target: url,
direction: 'OUT',
headers: filterHeaders(headers)
},
request: _.pick(options, ['url', 'method', 'headers', 'body', 'json'])
});
}
],
beforeRetry: [
(options, error, retryCount) => {
const {
response: { statusCode, ip } = {},
request: { options: { method, headers = {} } = {}, requestUrl: url } = {},
timings: {
// eslint-disable-next-line camelcase
phases: { total: duration_ms } = {}
} = {}
} = error;
logger.warn({
message: `${name} will retry request, attempt ${retryCount}/${options.retry.limit} ${method} ${url} (${error.code} ${error.message})`,
err: error,
http_request: {
method,
target: url,
status: statusCode,
server_ip: ip,
duration_ms,
direction: 'OUT',
protocol: headers.via,
headers: filterHeaders(headers)
}
});
}
],
beforeError: [
error => {
const {
response: { statusCode, ip } = {},
request: { options: { method, headers } = {}, requestUrl: url } = {},
timings: {
// eslint-disable-next-line camelcase
phases: { total: duration_ms } = {}
} = {}
} = error;
if (!statusCode) {
logger.error({
message: `${name} request error ${method} ${url} (${error.code} ${error.message})`,
err: error,
http_request: {
method,
target: url,
status: statusCode,
server_ip: ip,
duration_ms,
direction: 'OUT',
protocol: headers.via,
headers: filterHeaders(headers)
}
});
}
// eslint-disable-next-line no-param-reassign
error.serviceName = name;
return error;
}
],
afterResponse: [
response => {
const {
statusCode,
body,
url,
ip,
headers = {},
request: { options: { method } = {} } = {},
timings: {
// eslint-disable-next-line camelcase
phases: { total: duration_ms } = {}
} = {},
retryCount
} = response;
logger.debug({
message: `${name} response ${method} ${url}`,
response: { body, retryCount, headers },
http_request: {
method,
target: url,
status: statusCode,
server_ip: ip,
duration_ms,
direction: 'OUT',
protocol: headers.via,
headers: filterHeaders(_.get(response, 'request.options.headers'))
}
});
return response;
}
]
}
});
const gotClient = ({ name, logger, keepAlive = true, gotOptions = {} }) => {
if (!clients[name]) {
clients[name] = got
.extend({
headers: {
'user-agent': `${packageName} ${packageVersion}`
},
...(keepAlive && {
agent: {
http: new http.Agent(keepAliveOptions),
https: new https.Agent(keepAliveOptions)
}
}),
responseType: 'json',
timeout: 5000
})
.extend(gotLoggingHooks(name, logger))
.extend(gotOptions);
}
return clients[name];
};
gotClient.clearAll = () => {
clients = {};
};
module.exports = gotClient;
got-client.spec.js below
const nock = require('nock');
const { name: packageName, version: packageVersion } = require('../../../package.json');
const gotClient = require('../../../src/lib/got-client');
const BASE_URL = 'https://subdomain.domain.com/';
const BASE_ENDPOINT = 'path';
const logger = {
error: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
log: jest.fn(),
warn: jest.fn(),
};
describe('got client', () => {
afterEach(gotClient.clearAll);
test('should log requests', async () => {
const client = gotClient({
name: 'test',
logger,
gotOptions: {
prefixUrl: BASE_URL,
},
});
nock(BASE_URL).get(`/${BASE_ENDPOINT}`).reply(200, { success: true });
await client.get(BASE_ENDPOINT);
// console.log('mock call 0', logger.debug.mock.calls[0][0]);
// TODO: match message
expect(logger.debug).toBeCalled();
expect(logger.debug).toBeCalledWith(
expect.objectContaining({
message: expect.stringContaining(`request GET ${BASE_URL}${BASE_ENDPOINT}`),
})
);
expect(logger.debug).toBeCalledWith(
expect.objectContaining({
message: expect.stringContaining(`response GET ${BASE_URL}${BASE_ENDPOINT}`),
})
);
nock(BASE_URL).get(`/${BASE_ENDPOINT}/error`).reply(500, { success: false });
try {
await client.get(`${BASE_ENDPOINT}/error`, { retry: 0 });
} catch (e) {}
expect(logger.error).toBeCalledWith(
expect.objectContaining({
message: expect.stringContaining(`request error GET ${BASE_URL}${BASE_ENDPOINT}/error`),
})
);
});
});
Failing Test Error below
Error: expect(jest.fn()).toBeCalledWith(...expected)
Expected: ObjectContaining {"message": StringContaining "request error GET https://subdomain.domain.com/path/error"}
Number of calls: 0
at Object.<anonymous> (/Users/user/Documents/company/teams/team/project/test/unit/lib/got-clients.spec.js:62:26)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
I will really appreciate help with this. Thank you very much in advance.
Working got-client.spec.js
const nock = require('nock');
const { name: packageName, version: packageVersion } = require('../../../package.json');
const gotClient = require('../../../src/lib/got-client');
const BASE_URL = 'https://subdomain.domain.com/';
const BASE_ENDPOINT = 'path';
const logger = {
error: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
log: jest.fn(),
warn: jest.fn(),
};
const defaultClient = gotClient({
name: 'test',
logger,
gotOptions: {
prefixUrl: BASE_URL,
},
});
describe('got client', () => {
afterEach(gotClient.clearAll);
test('should log requests', async () => {
nock(BASE_URL).get(`/${BASE_ENDPOINT}`).reply(200, { success: true });
await defaultClient.get(BASE_ENDPOINT);
expect(logger.debug).toBeCalledWith(
expect.objectContaining({
message: expect.stringContaining(`request GET ${BASE_URL}${BASE_ENDPOINT}`),
})
);
});
test('should log responses', async () => {
nock(BASE_URL).get(`/${BASE_ENDPOINT}`).reply(200, { success: true });
await defaultClient.get(BASE_ENDPOINT);
expect(logger.debug).toBeCalledWith(
expect.objectContaining({
message: expect.stringContaining(`response GET ${BASE_URL}${BASE_ENDPOINT}`),
})
);
});
test('should log errors', async () => {
const endpoint = `${BASE_ENDPOINT}/error`;
nock(BASE_URL).get(`/${endpoint}`).replyWithError({
message: 'something awful happened',
code: 'ECONNRESET',
});
try {
await defaultClient.get(endpoint, { retry: 0 });
} catch (e) {}
expect(logger.error).toBeCalledWith(
expect.objectContaining({
message: expect.stringContaining(`request error GET ${BASE_URL}${endpoint}`),
})
);
});
test('should log retries', async () => {
nock(BASE_URL)
.get(`/${BASE_ENDPOINT}`)
.replyWithError({
message: 'something awful happened',
code: 'ECONNRESET',
})
.get(`/${BASE_ENDPOINT}`)
.reply(500, { success: false })
.get(`/${BASE_ENDPOINT}`)
.reply(500, { success: false })
.get(`/${BASE_ENDPOINT}`)
.reply(200, { success: true });
await defaultClient.get(BASE_ENDPOINT, { retry: { limit: 3, calculateDelay: () => 1 } });
expect(logger.warn).toBeCalledTimes(3);
expect(logger.warn).toBeCalledWith(
expect.objectContaining({
message: expect.stringContaining(`will retry request`),
})
);
});
});
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,
})...