Calling AxiosRef with method and data as empty object not working - axios

I am working in a Nest TypeScript work where I would like to call a http request using AxiosRefby passing the method.
Means instead of calling as this.httpService.axiosRef.get(url, {headers}) I would like to invoke as this.httpService.axiosRef({method, url, headers}).
And there I am seeing some issue:
Here is my working code snippet:
async request<T = any>(creds: CredentialObj, method: Method, data: any, query?: Record<string, string>): Promise<T> {
const headers = this.getHeaders(creds);
const timeout = +(process.env.HTTP_CALL_TIMEOUT || 10000);
const url: string = `<the URL>`;
return this.httpService
.axiosRef.get(url, { headers })
.then((response: AxiosResponse<T>) => this.handleHttpResponse(response))
.catch((error: AxiosError) => this.handleHttpReject(error));
}
But If I change the axiosRef like this:
console.log(`Method: ${method}`);
console.log(`Data: ${JSON.stringify(data)}`);
return this.httpService
.axiosRef({ method, url, headers, data})
.then((response: AxiosResponse<T>) => this.handleHttpResponse(response))
.catch((error: AxiosError) => this.handleHttpReject(error));
It does not work and gives an error:
Method: get
Data: {}
Error: Request failed with status code 400
at createError (..\node_modules\axios\lib\core\createError.js:16:15)
at settle (...\nest-services\node_modules\axios\lib\core\settle.js:17:12)
at IncomingMessage.handleStreamEnd (C:\Users\pradipm\clients\CloudManager\cm_12\occm\service-infra\nest-services\node_modules\axios\lib\adapters\http.js:260:11)
at IncomingMessage.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1358:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)
It's just an empty data object.
Actually I want to make it like this way of passing the method name such that I can use it for all REST API verbs as a common util routine. For cases other than get (e.g. post, patch) we need to pass the payload. Hence trying to make a single utility for the same.
My axios version is: "axios": "^0.21.1",

Related

How to get headers from AxiosResponse-Observable?

Using NestJS, Axios returns an Observable<AxiosResponse>.
How can I get the headers of a GET- or HEAD-Request?
Lets say I make a HEAD-request:
import { HttpService } from '#nestjs/axios';
const observable = this.httpService.head(uri);
How can I get the headers from the result?
Update:
I found a nice workaround that just works with a single line of code.
There is another library called https with is more powerful:
import http from "https";
await http.request(uri, { method: 'HEAD' }, (res) => {
console.log(res.headers);
}).on('error', (err) => {
console.error(err);
}).end();
The headers of the response are available in the subscribe callback with the headers property.
this.httpService.head(uri).subscribe(res => {
console.log(res.headers)
});
Playground
According to https://github.com/axios/axios#request-config:
For request headers you should use something like this:
this.httpService.axiosRef.interceptors.request.use(function (config) {
// Do something before request is sent
console.log(config);
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
You should use it onModuleInit (to prevent working a few interceptors in a time)
Also you can make own module like in this answer: https://stackoverflow.com/a/72543771/4546382
For the response headers you can just use response.headers

Access HTTP Response Body on Axios GET Request Error

We are making a simple HTTP GET request to an API & noticing that when our HTTP requests error, the response body sometimes has more details then the error message. We are using the axios. We would like to access both the response and the error message from our code, but we are only seeing the error message and are unsure how to access the response. Here is our code:
Calling the API:
import { httpClient } from '../httpClient'; //AxiosInstance
type GetLocations = () => Promise<AxiosResponse<ILocation[]>>
const getLocations: GetLocations = async () => {
const url = `/Location`;
return httpClient.get(url);
}
const callAPI = async () => {
try {
const axiosResponse = await getLocations();
console.log(axiosResponse.data);
catch(error) {
// error.message has the error message, but we would like access to the http response body as well.
console.log(error)
}
}
Some Screenshots:
console.log(error):
The error object as JSON:
DevTools > Network > Our API Call > Response from same request that errored (has more info):
We would like to access this Incorrect syntax near ','. response from our catch block.

axios / jest - unabled to perform a call request (TypeError: Cannot read property 'then' of undefined)

I'm struggling to perform a test with jest concerning an axios api call
here is my API call, that works perfectly within my program
import axios from 'axios';
import crypto from 'crypto';
import { prop } from 'ramda';
const baseUrl = 'http://gateway.marvel.com:80';
const uri = '/v1/public/characters';
const charactersUrl = baseUrl + uri;
const timestamp = [Math.round(+new Date() / 1000)];
const privateApi = 'XXX';
const publicApi = 'XXX';
const concatenatedString = timestamp.concat(privateApi, publicApi).join('');
const hash = crypto.createHash('md5').update(`${concatenatedString}`).digest('hex');
const charactersApi = () =>
axios
.get(charactersUrl, {
params: {
ts: timestamp,
apikey: publicApi,
hash,
},
})
.then(prop('data'));
export default charactersApi;
When I'm trying to test it, that way:
import axiosMock from 'axios';
import charactersApi from '../marvelApi';
jest.mock('axios', () => ({
get: jest.fn(),
}));
describe('tools | marvelApi', () => {
const piece = { name: '3D-MAN' };
axiosMock.get.mockResolvedValueOnce({ data: piece });
it('should get the character', () => {
return charactersApi().then(elem => {
expect(elem.name).toEqual('3D-MAN');
});
});
});
I get the following message from jest
TypeError: Cannot read property 'then' of undefined
16 |
17 | const charactersApi = () =>
> 18 | axios
| ^
19 | .get(charactersUrl, {
20 | params: {
21 | ts: timestamp,
at charactersApi (src/tools/marvelApi.js:18:3)
at Object.<anonymous> (src/tools/tests/marvelApi.test.js:13:12)
What I have tried
A common error is to forget the return statement within the function that contain the request API, in my case it's done correctly (first piece of code -> charactersApi()) source1, source2
I also tried to return a Promise from the mocked Axios as I have seen on another SO ticket
jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve()),
}));
I think my axios mock is not correct, because the struggle comes from the test while the production version work well
Any thoughts ?
You can spy on the "axios.get" calls and resolve them to a fixed (mocked) value:
/**
* #jest-environment jsdom
*/
const axios = require('axios')
beforeAll(() => {
jest.spyOn(axios, 'get').mockImplementation()
})
afterAll(() => {
jest.restoreAllMocks()
})
it('returns the mocked response', async () => {
axios.get.mockResolvedValue({ data: 'foo' })
const res = await axios.get('https://api.github.com')
expect(res).toEqual({ data: 'foo' })
})
You shouldn't use jest.mock because it mocks a module that your imported code may be using. As far as I know, it doesn't affect the current module's imports (and you import axios as a part of your test).
Recommended solution
I strongly discourage you from spying/mocking axios directly. See my argumentation below.
You're mocking implementation details of axios. In other words, you take the axios.get function and throw it away, alongside any internal logic it may have, and replace it with a hard mock. This means your test no longer uses axios, instead it uses an emptied mocked shell of axios. This makes your test different from your actual code, which, in turn, decreases the confidence such a test gives you.
You're coupling your mocks with a specific request client (axios). Such an approach is not a long-term investment, as you're writing axios-specific mocks. You can't reuse such mocks for requests made by other clients (i.e. window.fetch, Apollo, etc.), because they have their own implementation details (i.e. window.fetch has no .get() to spy on), which only encourages you to write more implementation-specific logic in tests.
You can learn more about the disadvantages of direct mocking of request clients in the Stop mocking fetch article by Kent C. Dodds. It uses window.fetch mocks as an example, but you may replace it with ANY_REQUEST_CLIENT when reading.
I highly recommend using tools like Mock Service Worker (MSW) that will encourage you to write abstracted mocks that don't rely on any request clients (you can use them no matter how your tested code makes a request) and can even be reused across different testing levels (the same mocks for Jest, Storybook, or Cypress).
Here's how your test would look like with MSW:
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import charactersApi from '../marvelApi';
const server = setupServer(
rest.get('http://gateway.marvel.com:80/v1/public/characters', (req, res, ctx) => {
return res(ctx.json({
data: {
name: '3D-MAN'
}
}))
})
)
beforeAll(() => server.listen()
afterAll(() => server.close())
describe('tools | marvelApi', () => {
it('should get the character', () => {
return charactersApi().then(elem => {
expect(elem.name).toEqual('3D-MAN')
})
})
})
Notice how there are no details about how the request is made, only which request to intercept and mock its response.
You can follow a detailed tutorial on how to Get started with MSW. There's also a great video on API mocking and what problems MSW solves.

axios DELETE request with body in Nuxt.js

I have an app built with Nuxt.js. To get data from the API I use axios, namely #nuxtjs/axios. All request work fine but for the DELETE method.
My syntax is the following:
async removeItemFromCart(productId) {
const accessKey = await this.$store.dispatch('fetchUserAccessKey');
try {
this.$axios.delete(`/api/baskets/products?userAccessKey=${ accessKey }`, {
data: {
productId: productId
}
})
} catch (error) {
console.log(error);
}
},
However, in the console I always get the following error: createError.js?2d83:16 Uncaught (in promise) Error: Request failed with status code 400
I tried to use params instead of data, but to no avail. What am I missing here?
It seems that there's an issue with axios: when you use delete method with body, it either doesn't include payload, or deletes Content-type: 'application/json' from headers. To solve the issue, I used .request instead of .delete (according to https://github.com/nuxt-community/axios-module/issues/419)
this.$axios.request(`/api/baskets/products?userAccessKey=${ accessKey }`, {
data: {
productId: productId
},
method: 'delete'
}).

Escape " in axios

I am making an api call to WuFoo forms
to do a dateCreated filter it needs to look like this (note double quote):
Filter1=DateCreated+Is_greater_than+"2019-11-13 12:00:00"
However, Axios urlencodes it to look like this:
Filter1=DateCreated%2BIs_greater_than%2B%222019-11-13+12:00:00%22'
and WuFoo unfortunately returns incorrect response to that.
I have tried escaping the encoding by using paramSerializer:
const instance = axios.create({
baseURL: 'https://subdomain.wufoo.com/api/v3',
timeout: 1000,
headers: { 'Authorization': 'Basic fjdkalfjkdafldklaskflsdkl' },
paramsSerializer: function(params) { return params }
});
....
instance.get('/forms/form/entries.json',{
params:{
Filter1: qDateFilter
}
})
qDateFilter = DateCreated+Is_greater_than+"2019-11-13 12:00:00"
However I now have the following error:
TypeError [ERR_UNESCAPED_CHARACTERS]: Request path contains unescaped characters
at new ClientRequest (_http_client.js:139:13)
at Object.request (https.js:309:10)
at RedirectableRequest._performRequest (/home/node/app/node_modules/follow-redirects/index.js:169:24)
at new RedirectableRequest (/home/node/app/node_modules/follow-redirects/index.js:66:8)
at Object.wrappedProtocol.request (/home/node/app/node_modules/follow-redirects/index.js:307:14)
at dispatchHttpRequest (/home/node/app/node_modules/axios/lib/adapters/http.js:180:25)
at new Promise (<anonymous>)
at httpAdapter (/home/node/app/node_modules/axios/lib/adapters/http.js:20:10)
at dispatchRequest (/home/node/app/node_modules/axios/lib/core/dispatchRequest.js:59:10)
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
code: 'ERR_UNESCAPED_CHARACTERS'
}
Attempts to just use a straight full string as a URL don't work either, it still encodes it.
Using straight " in postman works fine, same with curl.
Any other options?
WuFoo got back to me. It wasn't the " it was actually the encoding of the + sign. I replaced with spaces and it worked.