Skip to content

Commit

Permalink
feat: add fetchData to utils package
Browse files Browse the repository at this point in the history
- improve the code
- add unit test
  • Loading branch information
AliKdhim87 committed Sep 27, 2024
1 parent cfe281a commit f27467a
Show file tree
Hide file tree
Showing 2 changed files with 314 additions and 0 deletions.
216 changes: 216 additions & 0 deletions packages/utils/src/fetchData/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/* eslint-disable no-undef */
import { fetchData } from './index';

const url = 'https://api.example.com/graphql';
const query = `
query {
users {
id
name
}
}
`;

describe('fetchData', () => {
it('should fetch data successfully', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: { users: [] } }),
ok: true,
status: 200,
statusText: 'OK',
}),
) as jest.Mock;
global.fetch = mockFetch;

const data = await fetchData({ url, query });
expect(data).toEqual({ data: { users: [] } });
expect(mockFetch).toHaveBeenCalledWith(url, {
method: 'POST',
cache: 'no-store',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query }),
});
});
it('should have POST method by default', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: { users: [] } }),
ok: true,
status: 200,
statusText: 'OK',
}),
) as jest.Mock;
global.fetch = mockFetch;
const data = await fetchData({ url, query });
expect(data).toEqual({ data: { users: [] } });
expect(mockFetch).toHaveBeenCalledWith(url, {
method: 'POST',
cache: 'no-store',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query }),
});
});
it('should handle GET method', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: { users: [] } }),
ok: true,
status: 200,
statusText: 'OK',
}),
) as jest.Mock;
global.fetch = mockFetch;
const data = await fetchData({ url: 'https://example.com/api/users', method: 'GET' });
expect(data).toEqual({ data: { users: [] } });
expect(mockFetch).toHaveBeenCalledWith('https://example.com/api/users', {
method: 'GET',
cache: 'no-store',
headers: {
'Content-Type': 'application/json',
},
});
});
it('should handle cache', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: { users: [] } }),
ok: true,
status: 200,
statusText: 'OK',
}),
) as jest.Mock;
global.fetch = mockFetch;

const data = await fetchData({ url, query });
expect(data).toEqual({ data: { users: [] } });
expect(mockFetch).toHaveBeenCalledWith(url, {
cache: 'no-store',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query }),
});
});
it('should handle network error', async () => {
const mockFetch = jest.fn(() => Promise.reject(new Error('Network error')));
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Network error');
});
it('should handle error', async () => {
const mockFetch = jest.fn(() => Promise.reject(new Error('Fetch failed')));
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow();
});
it('should handle error with status code', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ errors: [{ message: 'Not found' }] }),
ok: false,
status: 404,
}),
) as jest.Mock;
global.fetch = mockFetch;

expect(fetchData({ url, query })).rejects.toThrow('Not found');
});
it('should handle error with status code 400', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ errors: [{ message: 'Bad request' }] }),
ok: false,
status: 400,
statusText: 'Bad request',
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Bad request');
});
it('should handle error with status code 403', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ errors: [{ message: 'Forbidden' }] }),
ok: false,
status: 403,
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Forbidden');
});
it('should handle error with status code 404', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 404,
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Not found');
});
it('should handle error with status code 422', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 422,
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Unprocessable entity');
});
it('should handle error with status code 500', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 500,
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Internal server error');
});
it('should handle error with status code 503', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 503,
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Service unavailable');
});
it('should handle error with status code 504', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 504,
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Gateway timeout');
});
it('should handle error with status code 505', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 505,
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('HTTP version not supported');
});
it('should handle error with custom status code', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 600,
statusText: 'Unknown error',
}),
) as jest.Mock;
global.fetch = mockFetch;
expect(fetchData({ url, query })).rejects.toThrow('Unknown error');
});
});
98 changes: 98 additions & 0 deletions packages/utils/src/fetchData/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { ErrorHandler } from '../errorHandler';

interface HandleGraphqlRequestProps {
query?: string;
variables: any;
}

const handleGraphqlRequest = ({ query, variables }: HandleGraphqlRequestProps) =>
({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
cache: 'no-store',
}) as RequestInit;

export interface FetchDataProps {
url: string;
query?: string;
variables?: any;
method?: string;
}

/**
* @description This function is used to fetch data from the server
* @param {string} url - The url to fetch data from
* @param {string} query - The query to fetch data from the GraphQL API server
* @param {any} variables - The variables to pass it to the GraphQL Queries
* @param {string} method - The method to use for fetching data, default is POST
* @returns {Promise<T>} - The data fetched from the server
* @example
* GraphQL Example:
* const data = await fetchData<Users>({ url: 'https://example.com/graphql', query: 'query { users { id name } }' });
* API Example:
* const data = await fetchData<Users>({ url: 'https://example.com/api', method: 'GET' });
* */
// The POST method is set as the default option because it is commonly used to fetch data from GraphQL.
export const fetchData = async <T>({ url, query, variables, method = 'POST' }: FetchDataProps): Promise<T> => {
const defaultOptions = {
method,
cache: 'no-store',
headers: {
'Content-Type': 'application/json',
},
} as RequestInit;
const graphqlOption = handleGraphqlRequest({ query, variables });
try {
const response = await fetch(url, query ? graphqlOption : defaultOptions);
if (!response.ok) {
const { logger } = new ErrorHandler();
logger();
switch (response.status) {
case 400:
throw new ErrorHandler(response.statusText, {
statusCode: 400,
});
case 403:
throw new ErrorHandler('Forbidden', {
statusCode: 403,
});
case 404:
throw new ErrorHandler('Not found', {
statusCode: 404,
});
case 422:
throw new ErrorHandler('Unprocessable entity', {
statusCode: 422,
});
case 500:
throw new ErrorHandler('Internal server error', {
statusCode: 500,
});
case 503:
logger();
throw new ErrorHandler('Service unavailable', {
statusCode: 503,
});
case 504:
throw new ErrorHandler('Gateway timeout', {
statusCode: 504,
});
case 505:
throw new ErrorHandler('HTTP version not supported', {
statusCode: 505,
});
default:
throw new ErrorHandler(response.statusText, {
statusCode: response.status,
});
}
}
const data = await response.json();
return data;
} catch (error: any) {
throw new ErrorHandler(error?.message, {
statusCode: error?.options?.statusCode,
});
}
};

0 comments on commit f27467a

Please sign in to comment.