How to test an async function with callback in Jest? - javascript

I'm having a hard time finding info on how to test this function:
const MyService = {
async stringify (entry, cb) {
try {
const response = await axios.post('localhost:3005/stringify', {
entry
})
cb(null, response.data)
} catch (minificationError) {
if (minificationError.response.status === 500) {
cb('error 1', null)
} else {
cb('error 2', null)
}
}
}
}
I understand I can import axios and mock the .post like this:
axios.post.mockResolvedValue({
data: { some: 'value' }
})
That'd work great if I the MyService was returning the promise... but how do I deal with the callback? Is this a bad practice and should the service be returning the promise and then handle errors in the component functions instead?
Additionally, how would I mock a status code with jest (to test the failed states?)

First, you have to set up mock axios after that you have to call your mockapi's in your test case
const axios = {
post: jest.fn(() => {
return Promise.resolve({
data: {},
});
}),
create: () => axios,
request: {},
defaults: {
adapter: {},
headers: {},
},
interceptors: {
request: {
use() {},
},
response: {
use() {},
},
},
};
Once you setup mock axios then you can access in your test case and return whatever mock response and status code you want.
mockAxios.post.mockImplementation((url) => {
if (url.includes("something")) {
return Promise.resolve({ data:{"response":""}, status: 200 });
}
return Promise.reject(new Error("not found"));
});

Related

URQL WSS connection with GraphQL-WS says error 4500

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.

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,
})...

Vue.js and Axios: Transform Request returns undefined data

I was trying to use transformRequest and transformResponse in order to display a loader while data is rendering. When I have done this, it worked perfectly but it is appeared that my POST and PATCH requests are not processed properly now. There are no error messages on console but when I try to modify some entity (and send PATCH request), nothing happens. When I delete transformRequest function, I can send POST and PATCH.
Can somebody explain what I did wrong?
UPDATE! Here is what I added in http/index.js:
...
transformRequest(data) {
console.log(data); // returns undefined
store.dispatch('loadingData');
console.log(data) // returns undefined
return console.log(JSON.stringify(data)); // returns undefined
},
transformResponse(data) {
store.dispatch('finishLoadingData');
const parsedData = JSON.parse(data);
if (parsedData.error === 'Access denied') {
cookies.remove('access_token');
window.location = `${config.mainSite}/login`;
}
return parsedData;
},
...
Here is the store:
export default {
state: {
loading: false,
},
actions: {
loadingData({ commit }) {
commit('LOADING_DATA');
},
finishLoadingData({ commit }) {
commit('FINISH_LOADING_DATA');
},
},
mutations: {
LOADING_DATA(state) {
state.loading = true;
},
FINISH_LOADING_DATA(state) {
state.loading = false;
},
},
getters: {
getLoading(state) {
return state.loading;
},
},
};
As we use Axios, transformRequest and transformResponse accepts an array of functions and not a single function. Can you try this:
...
transformRequest: [(data) => {
store.dispatch('loadingData');
return data;
}],
transformResponse: [(data) => {
store.dispatch('finishLoadingData');
const parsedData = JSON.parse(data);
if (parsedData.error === 'Access denied') {
cookies.remove('access_token');
window.location = `${config.mainSite}/login`;
}
return parsedData;
}],
...

Axios ajax, show loading when making ajax request

I'm currently building a vue app and Im using axios. I have a loading icon which i show before making each call and hide after.
Im just wondering if there is a way to do this globally so I dont have to write the show/hide loading icon on every call?
This is the code I have right now:
context.dispatch('loading', true, {root: true});
axios.post(url,data).then((response) => {
// some code
context.dispatch('loading', false, {root: true});
}).catch(function (error) {
// some code
context.dispatch('loading', false, {root: true});color: 'error'});
});
I have seen on the axios docs there are "interceptors" but II dont know if they are at a global level or on each call.
I also saw this post for a jquery solution, not sure how to implement it on vue though:
$('#loading-image').bind('ajaxStart', function(){
$(this).show();
}).bind('ajaxStop', function(){
$(this).hide();
});
I would setup Axios interceptors in the root component's created lifecycle hook (e.g. App.vue):
created() {
axios.interceptors.request.use((config) => {
// trigger 'loading=true' event here
return config;
}, (error) => {
// trigger 'loading=false' event here
return Promise.reject(error);
});
axios.interceptors.response.use((response) => {
// trigger 'loading=false' event here
return response;
}, (error) => {
// trigger 'loading=false' event here
return Promise.reject(error);
});
}
Since you could have multiple concurrent Axios requests, each with different response times, you'd have to track the request count to properly manage the global loading state (increment on each request, decrement when each request resolves, and clear the loading state when count reaches 0):
data() {
return {
refCount: 0,
isLoading: false
}
},
methods: {
setLoading(isLoading) {
if (isLoading) {
this.refCount++;
this.isLoading = true;
} else if (this.refCount > 0) {
this.refCount--;
this.isLoading = (this.refCount > 0);
}
}
}
demo
I think you are on the right path with dispatch event when ajax call start and finish.
The way that I think you can go about it is to intercept the XMLHttpRequest call using axios interceptors like so:
axios.interceptors.request.use(function(config) {
// Do something before request is sent
console.log('Start Ajax Call');
return config;
}, function(error) {
// Do something with request error
console.log('Error');
return Promise.reject(error);
});
axios.interceptors.response.use(function(response) {
// Do something with response data
console.log('Done with Ajax call');
return response;
}, function(error) {
// Do something with response error
console.log('Error fetching the data');
return Promise.reject(error);
});
function getData() {
const url = 'https://jsonplaceholder.typicode.com/posts/1';
axios.get(url).then((data) => console.log('REQUEST DATA'));
}
function failToGetData() {
const url = 'https://bad_url.com';
axios.get(url).then((data) => console.log('REQUEST DATA'));
}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<button onclick="getData()">Get Data</button>
<button onclick="failToGetData()">Error</button>
For Nuxt with $axios plugin
modules: ['#nuxtjs/axios', ...]
plugins/axios.js
export default ({ app, $axios ,store }) => {
const token = app.$cookies.get("token")
if (token) {
$axios.defaults.headers.common.Authorization = "Token " + token
}
$axios.interceptors.request.use((config) => {
store.commit("SET_DATA", { data:true, id: "loading" });
return config;
}, (error) => {
return Promise.reject(error);
});
$axios.interceptors.response.use((response) => {
store.commit("SET_DATA", { data:false, id: "loading" });
return response;
}, (error) => {
return Promise.reject(error);
})
}
store/index.js
export default {
state: () => ({
loading: false
}),
mutations: {
SET_DATA(state, { id, data }) {
state[id] = data
}
},
actions: {
async nuxtServerInit({ dispatch, commit }, { app, req , redirect }) {
const token = app.$cookies.get("token")
if (token) {
this.$axios.defaults.headers.common.Authorization = "Token " + token
}
let status = await dispatch("authentication/checkUser", { token })
if(!status) redirect('/aut/login')
}
}
}
This example is accompanied by a token check with $axios and store

Why does this mock api not work as expected?

I'm trying to test this simple api module:
import fetch from 'isomorphic-fetch';
export const getJson = (endpoint: string) => {
const options = { credentials: 'include', method: 'GET' };
return fetch(endpoint, options)
.then(response => response.json()
.then(json => {
if (response.ok) return json;
return Promise.reject(json.errors);
})
)
.catch(error => {
if (error.constructor === Array) return error;
return [error.message];
});
};
With this test, where I'm mocking fetch:
import { getJson } from '../api';
const mockResponse = (status, statusText, response) => {
return new window.Response(response, {
status: status,
statusText: statusText,
headers: {
'Content-type': 'application/json'
}
});
};
describe('api middleware', () => {
describe('getJson', () => {
it('should return the response on success', () => {
const expected = { data: ['data'], meta: {} };
const body = JSON.stringify(expected);
window.fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, body)));
return getJson('http://endpoint').then(actual => expect(actual).toEqual(expected));
});
});
});
But the test fails with:
Expected value to equal:
{"data": ["data"], "meta": {}}
Received:
["Unexpected end of JSON input"]
Difference:
Comparing two different types of values:
Expected: object
Received: array
I've not been able to figure out why this isn't working. Why am I receiving the "Unexpected end of JSON input" error? And how do I successfully mock fetch locally in a test? In this medium post it's done in basically the same way..
So apparently the test was still using the global fetch library, and not my patched version. The solution was to:
Remove the 'isomorphic-fetch' mock (in __mocks__ at the root of the project).
Import 'isomorphic-fetch' once at the root of my project with import 'isomorphic-fetch;
Remove the 'isomorphic-fetch' import at the top of my api module (since it's already imported at the entrypoint
Update the test to:
test:
// to make the Response constructor available
import 'isomorphic-fetch';
import { getJson } from '../api';
describe('api middleware', () => {
describe('getJson', () => {
beforeEach(() => {
window.fetch = jest.genMockFunction();
});
it('should return the response on success', () => {
const expected = { data: ['data'], meta: {} };
const body = JSON.stringify(expected);
const init = { status: 200, statusText: 'OK' };
window.fetch.mockReturnValueOnce(Promise.resolve(new Response(body, init)));
return getJson('http://endpoint').then(actual => expect(actual).toEqual(expected));
});
});
});
Most probably because your getJson function does not use the global (window) fetch.
The way I would suggest doing it is to use Dependency Injection (DI); make getJson retrieve the the "http request" library/function (in your case fetch) and in your tests, create a mock function which is injected. The mock function will return the data that you want as part of testing.

Categories

Resources