I am having issues mocking node-fetch in jest with ecma modules. No matter what I do, I can't seem to properly mock fetch. I have tried both direct mocking and using https://www.npmjs.com/package/jest-fetch-mock
I have tried everything I could think of and found online but nothing has worked. It never seems to properly stub fetch and always tries to make a real request.
node v18.12.0
package.json
{
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest",
},
"type": "module",
"dependencies": {
"node-fetch": "^3.3.0"
},
"devDependencies": {
"#jest/globals": "^29.3.1",
"jest": "^29.3.1",
"jest-fetch-mock": "^3.0.3",
}
}
jest.config.json
{
"testEnvironment": "node",
"resetMocks": true,
"resetModules": true,
"transform": {},
}
updateStatus.js
import fetch from 'node-fetch';
const updateStatus = async (url, status) => {
return await fetch(
url,
{
method: 'PUT',
headers: {
'X-API-KEY': process.env.API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ status }),
},
);
};
export default updateStatus;
updateStatus.test.js
import { jest } from '#jest/globals';
jest.mock('node-fetch');
import fetch from "node-fetch";
import updateStatus from '../../src/updateStatus';
beforeEach(() => {
process.env = { API_KEY: 'abc' };
});
describe('updateStatus', () => {
it('calls updateStatus', async () => {
const url = 'https://test.net?param1=abc';
const status = 'exStatus';
await updateStatus(url, status);
expect(fetch).toHaveBeenCalledWith(url, {
body: JSON.stringify({
status
}),
headers: {
'Content-Type': 'application/json',
'X-API-KEY': 'abc',
},
method: 'PUT',
});
});
});
At the moment I keep getting this error
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function fetch]
26 |
> 27 | expect(fetch).toHaveBeenCalledWith(url, {
Any help would be greatly appreciated!
PS: I tried using native fetch but can't use this in a production since it gives an experimental warning. But with native fetch I could get jest-fetch-mock to work...
Related
I am trying to use axios in nodejs typescript project and during the build it is throwing an error abortSignal any fix for this issue appreciate the help
index.ts
export async function getAccessToken(apiConfig: any) {
const req = {
grant_type: apiConfig.authOptions.body.grant_type,
client_id: apiConfig.authOptions.credentials.clientId,
client_secret: apiConfig.authOptions.credentials.clientSecret,
scope: apiConfig.authOptions.body.scope
};
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
"appName": "Blink"
};
try {
const resp: AxiosResponse = await axios.post('https://test.com//auth/oauth2/token',
req, { headers });
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
}
Error
node_modules/axios/index.d.ts(93,12): error TS2304: Cannot find name 'AbortSignal'.
Package.json
"axios": "^0.24.0",
Try providing abortController signal in the axios parameter:
const abortController=new AbortController;
const resp: AxiosResponse = await axios.post(
'https://test.com//auth/oauth2/token',
req,
{ headers },
signal:abortController.signal
);
I'm struggling a bit within a small project for fetching and creating (via POST) an entry, where I have on one side:
A GraphQL server (apollo)
A react app, using useQuery hook
A rest API, where the resolvers of the Apollo project is fetching data with async JS functions
I have the following obstacles:
I'm not able to post an entry for the rest API via GraphQl query or Mutation.
I have success in this post request:
POST https://technologytalents.io/space-cats/index.php/openapi/create_channel_entry
Accept: application/json
Content-Type: application/x-www-form-urlencoded
User-Agent: axios/0.21.1
channel_id=1&url_title=Blas&entry_date=12345678&title=Dooom&session_id=b288ea559b20c584a3a793685ceb20c240c26569
The success response of this is:
{entry_id: 2}
In my graphQL schema:
input entryIntput {
url_title: String
title: String
channel_id: Int
entry_date: Int
}
type postEntrySuccess {
entry_id: Int
}
type Mutation {
createEntry(input: entryIntput): postEntrySuccess
}
and in the resolvers:
Mutation: {
createEntry: async (_, entry) => await channelEntriesService.postEntry(entry)
}
my ChannelEntriesSerives looks like:
const axios = require('axios')
const authenticate = require('./authenticate')
class ChannelEntries {
constructor(options) {
this._options = options
}
async getEntries() {
const auth = await authenticate.auth()
const patch = {
...options,
url: `${options.url}/get_channel_entries?channel_id=1&where[status]=open&session_id=${auth.session_id}`
}
const response = await axios(patch)
return response.data
}
async postEntry(entry = { url_title: 'Blas', title: 'Dooom', entry_date: Date.now(), channel_id: 1 }) {
const auth = await authenticate.auth()
const patch = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
url: `${this._options.url}/create_channel_entry?channel_id=${entry.channel_id}&url_title=${entry.url_title}&title=${entry.title}&entry_date=${entry.entry_date}_id=${auth.session_id}`
}
const response = await axios.request(patch)
return response.data
}
}
const options = {
method: 'GET',
url: 'https://technologytalents.io/space-cats/index.php/openapi',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
module.exports = instance = new ChannelEntries(options)
When I try to execute the mutation on the GraphQl studio:
mutation CreateEntry($createEntryInput: entryIntput) {
createEntry(input: $createEntryInput) {
entry_id
}
}
I've got an error:
{
"errors": [
{
"message": "Request failed with status code 400",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createEntry"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"config": {
"url": "https://technologytalents.io/space-cats/index.php/openapi/create_channel_entry?channel_id=undefined&url_title=undefined&title=undefined&entry_date=undefined_id=b3c77d7c74b0cc10de61c90f8e1a34b30e454f7a",
"method": "post",
"headers": {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "axios/0.21.1"
},
"transformRequest": [
null
],
"transformResponse": [
null
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1
}
}
}
}
],
"data": {
"createEntry": null
}
}
What I'm doing wrong?
I found the reason for the error, and this is due to my rusty Axios basics. The config of an Axios request should have "data" property, so changing it to
const patch = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
url: `${this._options.url}/create_channel_entry`,
data: `channel_id=${entry.channel_id}&url_title=${entry.url_title}&title=${entry.title}&entry_date=${entry.entry_date}&session_id=${auth.session_id}`
}
returns the correct response.
The other issue is just a correct mapping of the response with graphql schema.
So i am trying to use the useHistory of react-router-dom package using the below code.
function AdminLogin() {
const LoginAct = async () => {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: hash })
};
await fetch('/post/admin/login', requestOptions).then(HandleResponse);
}
const HandleResponse = async (response) => {
return response.text()
.then(text => {
if (response.ok) {
var data = text && JSON.parse(text);
data = data[0];
if (data != null) {
LoginRoute();
}
}
})
}
function LoginRoute() {
const history = useHistory();
history.push('/student/app');
}
return (
// View here including button that calls LoginAct when clicked
);
}
export default AdminLogin;
However I am facing this error from const history = useHistory();:
I have tried to debug this with instructions in the URL shown in the error message. No luck!
My react and React DOM versions:
"peerDependencies": {
"react": "^17.0.2"
},
"dependencies": {
"react-dom": "^17.0.2",
},
I have placed the react package to peerDependecies as instructed in some of the answers here!
I have also tested other solutions found from the github issue above for ex. clearing my npm cache and updated all my packages
I have no idea what would be causing this problem other than me breaking Rules of Hooks, in which case I don't know how am I breaking them. (I also have eslint installed to enforce Rules of Hooks but it is possible that I have set it up wrong)
The hook needs to be called at the top level of the component. There's also no reason to abstract the single line into an additional callback, just do the PUSH in the asynchronous handler.
function AdminLogin() {
const history = useHistory(); // <-- here
const LoginAct = async () => {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: hash })
};
await fetch('/post/admin/login', requestOptions).then(HandleResponse);
}
const HandleResponse = async (response) => {
return response.text()
.then(text => {
if (response.ok) {
var data = text && JSON.parse(text);
data = data[0];
if (data != null) {
history.push('/student/app'); // <-- called here
}
}
})
}
return (
// View here including button that calls LoginAct when clicked
);
}
I have this working:
export default axios.create({
baseURL: 'sample',
headers: {
'Content-Type': 'application/json',
},
transformRequest: [
(data) => {
return JSON.stringify(data);
},
],
});
but the problem is once I edited to be like this:
const API = () => {
const token = 'sample'
const api: AxiosInstance = axios.create({
baseURL: 'http://localhost:5000',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
transformRequest: [
(data) => {
return JSON.stringify(data);
},
],
transformResponse: [
(data) => {
return JSON.parse(data);
},
],
});
return api;
};
export default API;
I want it to be an arrow function so I can access the token inside the function.
The problem is once I start to import the arrow function it will create an error not reading POST method
import API from 'apis';
API.post
Is there a way to implement it like an arrow function but will not lose the type definitions or create an error?
You don't loose any type definitions, but you're not using your import as a function.
If you write API().post it will work.
I would suggest doing the following:
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:5000',
headers: {
'Content-Type': 'application/json',
},
transformRequest: [
(data) => {
return JSON.stringify(data);
},
],
transformResponse: [
(data) => {
return JSON.parse(data);
},
],
});
import store from '../store'
const listener = () => {
const token = store.getState().token
api.defaults.headers.common['Authorization'] = token;
}
store.subscribe(listener)
export default api;
You can access the token here as well.
Just because this is the question you find when you look for my problem, if you use require to import axios, to use the correct type definition you'll have to import ti like that:
const axios = require('axios').default
axios.create(...) // No error
This would give an error:
const axios = require('axios')
axios.create(...) // Error
I am not able to get accessToken, i need it in my backend api.
When i try this,
googleLogin = () => {
GoogleSignin.signIn()
.then((data) => {
console.log("TEST "+JSON.stringify(data));
var postData = {
access_token: data.accessToken,
code: data.idToken,
};
let axiosConfig = {
headers: {
'Content-Type': 'application/json',
"Accept": "application/json",
}
};
....
//Backend api axios call
....
})
.then((user) => {
console.log("TEST G LOGIN 1 "+JSON.stringify(user))
})
.catch((error) => {
console.log("....."+JSON.stringify(error))
});
}
got this response, it doesn't inculde
accessToken
{
"scopes": ["https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/drive.readonly"],
"serverAuthCode": "4/jwE5LLLLLLa7-f33333jmMD2V978oyp44444eb9yGe_qhHnkXXXXXXXLFXKZlQdDzlH-iIJx_gzqlLLLLLL3Q0PP0",
"idToken": "...v65a4MCC-ZUQmys_wf_DoCOBJEMuI........",
"user": {
"photo": "https://lh3.googleusercontent.com/-tLLLLLyeS0KE/AAAMMMMAAAI/AAAAAAAAAAA/ACHi3reMhihoeTe_6NjL666666EUVU82Q/s96-c/photo.jpg",
"email": "test#gmail.com",
"familyName": "tech",
"givenName": "test",
"name": "testtech",
"id": "11688888817288868"
}
}
as per documentation
getTokens() Resolves with an object containing { idToken: string,
accessToken: string, } or rejects with an error. Note that using
accessToken is discouraged.
So, i tried this in
GoogleSignin.Sign({
....
var gettoken = GoogleSignin.currentUserAsync(data.user).then((token) => {
console.log('USER token', token);
}).done();
...
})
it got error and also tried const token = GoogSignIn.getTokens(), it return null.
package.json info
{
...
"react": "16.8.3",
"react-native": "0.59.9",
"react-native-firebase": "5.3.1",
"react-native-google-signin": "^2.0.0"
...
}
Please suggest what would be procedure to get accessToken.
Finally i get accessToken.
Step 1:-
I deleted all the generated clidenId in goolge developer console (keep only web application clientId as i used in my web project) and also deleted android app in firebase project.
Step 2:-
Created new android app in firebase and download google-services.json and paste it in android/app/google-services.json
Step 3:-
Copied the clidenId from this part of google-services.json
...
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "xxxxxxx-xxxxx.apps.googleusercontent.com", //<--- copied this clientID
"client_type": 3
},
{
"client_id": "XXXXXXXXXX-fBBBBBBBBBBBBBBBBBBBBBBBBpugnhrade.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.projectios"
}
}
]
}
}
...
and paste in
GoogleSignin.configure({
webClientId: 'paste it here',
});
Step 4:-
This is the code to get accessToken
(But this code was not working in my previous google-services.json file)
googleLogin = () => {
GoogleSignin.signIn()
.then((data) => {
console.log("TEST " + JSON.stringify(data));
const currentUser = GoogleSignin.getTokens().then((res)=>{
console.log(res.accessToken ); //<-------Get accessToken
var postData = {
access_token: res.accessToken,
code: data.idToken,
};
let axiosConfig = {
headers: {
'Content-Type': 'application/json',
"Accept": "application/json",
}
};
-----
backend api call
-----
});
})
.then((user) => {
console.log("TEST G LOGIN 1 " + JSON.stringify(user))
})
.catch((error) => {
console.log("....." + JSON.stringify(error))
});
}