Skip to content
This repository has been archived by the owner on Aug 23, 2023. It is now read-only.

Commit

Permalink
feat(executeFetch): utilize composeFetch function
Browse files Browse the repository at this point in the history
Applying the newly added `composeFetch` function will allow consumers to extend the fetch abilities when using the thunk supplied `fetchClient`. By default the `composeFetch` function passes through the thunk supplied or base configured  `fetchClient` with no changes.

Co-authored-by: Michael A Tomcal <michael.a.tomcal@aexp.com>
Co-authored-by: James Singleton <james.singleton1@aexp.com>
  • Loading branch information
3 people committed Mar 10, 2020
1 parent ee5d3e1 commit 7f0608b
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 2 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ Thunk-provided `fetchClient` overrides `baseFetch` as the concerns of the
environment are more important such as providing cookie support on the server or
enforcing request timeouts.

To retain the ability to apply additional fetch functionality with a thunk-provided
`fetchClient` you may also extend the behavior by providing a `composeFetch`
function in the configuration that returns a composed fetch function.

```javascript
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { configureIguazuREST, resourcesReducer } from 'iguazu-rest';
Expand All @@ -113,6 +117,9 @@ configureIguazuREST({

// Overriden by thunk.withExtraArgument below
baseFetch: fetchWith6sTimeout,

// Restored functionality with composition
composeFetch: (customFetch) => composeFetchWith6sTimeout(customFetch),
});

/* Contrived custom fetch client */
Expand Down
114 changes: 114 additions & 0 deletions __tests__/actions/executeFetch.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,118 @@ describe('executeFetch', () => {
expect(config.baseFetch).toHaveBeenCalledTimes(1);
});
});
describe('composeFetch', () => {
// Helpers and Mocks
let fetchClient;
const mockFetch = jest.fn((fetch) => fetch);
const composeFetch = jest.fn((fetch) => mockFetch(fetch));

const setupFetchMock = () => {
Object.assign(config, {
// Create the mock once called
baseFetch: jest.fn((...args) => fetch.mockResponseOnce(
JSON.stringify({ id: '1234', name: 'bill' }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)(...args)),
composeFetch,
defaultOpts: { default: 'opt' },
resources: {
users: {
fetch: () => ({
url: 'http://api.domain.com/users/:id',
opts: {
resource: 'opt',
},
}),
},
},
});
// Create the mock once called
fetchClient = jest.fn((...args) => fetch.mockResponseOnce(
JSON.stringify({ id: '123', name: 'joe' }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)(...args));
};

beforeEach(() => {
mockFetch.mockClear();
composeFetch.mockClear();
});

it('should compose with baseFetch config and call resulting fetch func', async () => {
setupFetchMock();
const thunk = executeFetch({
resource, id, opts, actionType: 'LOAD',
});
const data = await thunk(dispatch, getState);
expect(data).toEqual({ id: '1234', name: 'bill' });
expect(fetch).toHaveBeenCalledWith(
'http://api.domain.com/users/123',
{
default: 'opt', method: 'GET', resource: 'opt', some: 'opt',
}
);
expect(dispatch).toHaveBeenCalledWith('waitAndDispatchFinishedThunk');
expect(config.baseFetch).toHaveBeenCalledTimes(1);
expect(fetchClient).not.toHaveBeenCalled();
expect(composeFetch).toHaveBeenCalledWith(config.baseFetch);
expect(mockFetch).toHaveBeenCalled();
});
it('should compose with fetchClient and call resulting fetch func', async () => {
setupFetchMock();

const thunk = executeFetch({
resource, id, opts, actionType: 'LOAD',
});
const data = await thunk(dispatch, getState, { fetchClient });
expect(data).toEqual({ id: '123', name: 'joe' });
expect(fetch).toHaveBeenCalledWith(
'http://api.domain.com/users/123',
{
default: 'opt', method: 'GET', resource: 'opt', some: 'opt',
}
);
expect(dispatch).toHaveBeenCalledWith('waitAndDispatchFinishedThunk');
expect(fetchClient).toHaveBeenCalledTimes(1);
expect(config.baseFetch).not.toHaveBeenCalled();
expect(composeFetch).toHaveBeenCalledWith(fetchClient);
expect(mockFetch).toHaveBeenCalled();
});
it('should not be called if not supplied and use baseFetch instead', async () => {
Object.assign(config, {
// Create the mock once called
baseFetch: jest.fn((...args) => fetch.mockResponseOnce(
JSON.stringify({ id: '1234', name: 'bill' }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)(...args)),
composeFetch: (fetch) => fetch,
defaultOpts: { default: 'opt' },
resources: {
users: {
fetch: () => ({
url: 'http://api.domain.com/users/:id',
opts: {
resource: 'opt',
},
}),
},
},
});
const thunk = executeFetch({
resource, id, opts, actionType: 'LOAD',
});
const data = await thunk(dispatch, getState);
expect(data).toEqual({ id: '1234', name: 'bill' });
expect(fetch).toHaveBeenCalledWith(
'http://api.domain.com/users/123',
{
default: 'opt', method: 'GET', resource: 'opt', some: 'opt',
}
);
expect(fetchClient).not.toHaveBeenCalled();
expect(config.baseFetch).toHaveBeenCalledTimes(1);
expect(composeFetch).not.toHaveBeenCalled();
expect(mockFetch).not.toHaveBeenCalled();
});
});
});
10 changes: 8 additions & 2 deletions src/actions/executeFetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ const actionTypeMethodMap = {
async function getAsyncData({
resource, id, opts, actionType, state, fetchClient,
}) {
const { resources, defaultOpts, baseFetch } = config;
const {
resources,
defaultOpts,
baseFetch,
composeFetch,
} = config;
const { url, opts: resourceOpts } = resources[resource].fetch(id, actionType, state);

const fetchOpts = merge.all([
Expand All @@ -60,8 +65,9 @@ async function getAsyncData({
const fetchUrl = buildFetchUrl({ url, id, opts: fetchOpts });

const selectedFetchClient = fetchClient || baseFetch;
const composedFetchClient = composeFetch(selectedFetchClient);

const res = await selectedFetchClient(fetchUrl, fetchOpts);
const res = await composedFetchClient(fetchUrl, fetchOpts);
const rawData = await extractDataFromResponse(res);
const { transformData } = config.resources[resource];
const data = transformData ? transformData(rawData, { id, opts, actionType }) : rawData;
Expand Down
1 change: 1 addition & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

const config = {
baseFetch: fetch,
composeFetch: (fetch) => fetch,
getToState: (state) => state.resources,
};

Expand Down

0 comments on commit 7f0608b

Please sign in to comment.