Testing a fetch.catch in custom hook - javascript

I've got this custom hook:
import React from 'react';
import { useMessageError } from 'components/Message/UseMessage';
export interface Country {
code: string;
name: string;
}
export default function useCountry(): Array<Country> {
const [countries, setCountries] = React.useState<Country[]>([]);
const { showErrorMessage } = useMessageError();
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => showErrorMessage());
}, []);
return countries;
}
I want to test catching an error if there will be invalid response. With that, error message should appear thanks to showErrorMessage(). And I've got this test:
const showErrorMessage = jest.fn();
jest.mock('components/Message/UseMessage', () => ({
useMessageError: () => ({
showErrorMessage: showErrorMessage,
}),
}));
import useCountry from 'components/Country/useCountry';
import { renderHook } from '#testing-library/react-hooks';
import { enableFetchMocks } from 'jest-fetch-mock';
enableFetchMocks();
describe('The useCountry hook', () => {
it('should show error message', async () => {
jest.spyOn(global, 'fetch').mockImplementation(() =>
Promise.resolve({
json: () => Promise.reject(),
} as Response),
);
const { result, waitForNextUpdate } = renderHook(() => useCountry());
await waitForNextUpdate();
expect(fetch).toHaveBeenCalled();
expect(showErrorMessage).toHaveBeenCalled();
expect(result.current).toEqual([]);
});
});
But with that, I'm getting an error:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error
What I'm doing wrong in here? I assume it is somehow related with await waitForNextUpdate();, but I really don't know for sure and how to manage with it.

waitForNextUpdate() waits for next update but your hook does not trigger it since it only calls showErrorMessage(). Take a look at this sandbox
As a straightforward solution something that triggers an update can be added:
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => {
showErrorMessage();
// trigger update in any suitable way, for example:
setCountries([]);
});
}, []);
But it may be better to refactor it in some way. For example, you could use a separate hook and state for errors:
export default function useCountry(): Array<Country> {
const [countries, setCountries] = React.useState<Country[]>([]);
const [error, setError] = React.useState(null);
const { showErrorMessage } = useMessageError();
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => setError(true));
}, []);
React.useEffect(() => {
if (error) {
showErrorMessage()
}
}, [error]);
return countries;
}

Related

How to run two function on useEffect at once on first render

useEffect(() => {
const getCategory = () => {
allcategory().then((res) => {
if (res.data.error) {
console.log("error");
setValues({ ...values,
message: res.data.error
});
} else {
setValues({ ...values,
categories: res.data.category,
formData: new FormData(),
})
}
})
}
const getPublisher = () => {
allpublisher().then((res) => {
if (res.data.error) {
console.log("error");
setValues({ ...values,
message: res.data.error
});
} else {
setValues({ ...values,
publishers: res.data.publisher,
formData: new FormData(),
});
}
})
}
getCategory()
getPublisher()
}, [])
When I try to add two function on useEffect it doesn't render any data but show data on first render if only one function is present on useEffect
Since you are using promises, you can implement promise chaining here like
getCategory().then(()=>getPublisher())
But instead of having multiple promise chains, you can implement async await functions
const getData = async() => {
await getCategory()
await getPublisher()
}
useEffect(() => {
getData()
},[])
I think you miss async function.
I think this should like this
const getCategory = async () =>
const getPublisher = async () =>
And with my opinion, you should refractor like this:
const getCategory = async () => {
// ...do sth
}
const getPublisher = async () => {
// ...do sth
}
useEffect(() => {
getCategory();
getPublisher();
},[])

how to unit test if handler function is called when using middy

im using an http request function as the handler function in middy and then use the ssm middleware to fetch some ssm parameters before initiating the http request.
like this:
const makeThirdPartyServiceRequest = middy(async ({ params }) => {
logger.info(`SENDING Request to ${endpoint} API`)
const url = `https://someurltoathirdpartyservice`
const options = {
method: 'POST',
body: params
}
return helpers.makeRequest(url, options)
})
makeThirdPartyServiceRequest.use(ssm(......))
However in my jest unit test Im trying to mock makeThirdPartyServiceRequest and explicitly say it should resolve to a value:
jest.mock('../src/thirdPartyService', () => ({
__esModule: true,
default: {
...(jest.requireActual('../src/thirdPartyService') as { default: {} }).default,
makeThirdPartyServiceRequest: jest.fn()
}
}))
export {}
import thirdPartyService from '../src/thirdPartyService'
And then in the test i say:
describe('makeThirdPartyServiceRequest()', () => {
it('should makeThirdPartyServiceRequest', async () => {
// Given
// })
const mockedThirdPartyServiceRequest = mocked(thirdPartyService.makeThirdPartyServiceRequest).mockResolvedValue({})
// When
const result = await thirdPartyService.makeThirdPartyServiceRequest(something)
// Then
expect(mockedThirdPartyServiceRequest).toHaveBeenCalledTimes(1)
expect(mockedThirdPartyServiceRequest.mock.calls[0][0].params.toString()).toBe(expectedParams)
})
})
However for some reason the middy middleware is still being invoked, which i clearly dont want and i have tried to mock away... what am i doing wrong?
You need to mock middy instead, to make it becomes a useless function. That function recipe a function as a parameter and return that parameter.
import thirdPartyService from '../src/thirdPartyService'
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockReturnValue(handler), // ...use(ssm()) will return handler function
}
}
})
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
hoangdv answer is also valid, but i will answer as well how i continued.
if you completely want to mock middy you mock like following:
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockImplementation(() => {
// ...use(ssm()) will return handler function
return {
before: jest.fn().mockReturnValue(handler)
}
})
}
}
})
However if you dont want to completely mock middy, you can instead mock the async getInternal function from middy/util called in before like this:
jest.doMock('#middy/util', () => ({
...(jest.requireActual('#middy/util') as {}),
getInternal: jest.fn()
}))
import { getInternal } from '#middy/util'
and then in the test
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(getInternal).mockResolvedValue({
twilioSecrets: { accountSid: 'someSID', serviceId:
'someServiceID', token: 'someToken' }
})
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
this will mock the async part of middy.

How to mock multiple promise .then in react hooks and check for state set in one .then

The question could be seen as similar to this one but is not working really the same way as that one is checking for a function to be called while im looking for a state to change.
The code i have is this one (headers and body are not really important in this case):
const useGetToken = () => {
const [token, setToken] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const fetchToken = useCallback(() => {
setLoading(true);
fetch('http://localhost.something', {
headers,
body,
})
.then((response) => {
response.json();
})
.then((response) => {
setToken(response.access_token);
})
.catch((e) => {
setError(e);
})
.finally(() => {
setLoading(false);
});
}, []);
return { fetchToken, token, error, loading };
};
what I am trying to find is a way to test that the output I have is the correct one in case of success and in case of error.
Seems like I can mock until the first .then but then i dont know how to mock the second one.
import { renderHook, act } from '#testing-library/react-hooks';
describe('useGetToken', () => {
it('should fetch and return a token', () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => ({ access_token: 'aToken' }),
}),
);
const { result } = renderHook(() => useGetToken());
// also how to check for the fetchToken function to equal to itself i dont know how to do
// or maybe i can check if it is just a function
expect(result.current).toEqual({ token: 'aToken', loading: false, error: false });
});
});
managed a way to fix it changing with async await and the correct act.
also changed the double .then in the file to this
.then((response) => {
const parsedResponse = response.json();
setToken(parsedResponse.access_token);
})
cause i didnt need two
import { renderHook, act } from '#testing-library/react-hooks';
import useGetToken from '../useGetVfsToken';
describe('useGetToken', () => {
it('should fetch when fetchToken is called', async () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => ({ access_token: 'aToken123' }),
}),
);
const { result } = renderHook(() => useGetToken());
expect(window.fetch).not.toHaveBeenCalled();
await act(async () => {
result.current.fetchToken();
});
expect(window.fetch).toHaveBeenCalledTimes(1);
expect(result.current.token).toEqual('aToken123');
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
});
it('should have an error', async () => {
const error = 'an error text';
global.fetch = jest.fn().mockImplementation(() => Promise.reject(error));
const { result } = renderHook(() => useGetToken());
await act(async () => {
result.current.fetchToken();
});
expect(result.current.error).toEqual(error);
expect(result.current.loading).toEqual(false);
});
});

How to get the result of an async fetch request into my layout in gatsby js and react

I have the following fetchData async function returning a message froma lambda function I want to take that response and dump it onto my page I am using the react-hooks-async package, with a useEffect inside of it. However when I start the function isnide the useAsyncTask it just contiunally runs and never gets the result. I could do it if I hooked up a button to the start() function and it would display correct, but I want it to run on load.
I am using Gatsby JS and react
var fetchData = async function run() {
const response = await fetch(fetchUrl, {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: parsed.session_id,
}),
})
.then(res => {
return res.json()
})
.catch(error => console.log(error))
console.log(response)
return response
}
const Customer = () => {
const { start, started, result } = useAsyncTask(fetchData)
useEffect(() => {
console.log("result")
console.log(result)
console.log("result ends")
start()
}, [result])
return (
<div>
{started && "Fetching..."}
<div>Name: {result && result.message.customer_id}</div>
</div>
)
}
I was over engineering it. All that was required was the following
fetchData().then(value => console.log(value))
const Test = () => {
const [data, setData] = useState("")
useEffect(() => {
fetchData().then(test => {
setData(test)
})
}, [])
return data && <p>{data.message.customer_id}</p>
}

Dispatching an action in a debounced function using redux-thunk

I have the following debounced function that gets called every time a user inputs into the username field. It is working as expected.
export const uniqueUsernameCheck = _.debounce(({ username }) => {
axios.post(`${API_URL}/signup/usernamecheck`, { username })
.then((res) => {
console.log('Is unique?', res.data.status);
})
.catch((error) => {
console.log(error);
});
}, 500);
However using redux-thunk I am trying to modify the function so that I can dispatch actions within my function. This is what I have:
export const uniqueUsernameCheck = _.debounce(({ username }) => {
console.log('I can see this');
return (dispatch) => {
console.log('But not this');
dispatch({ type: USERNAME_CHECK });
axios.post(`${API_URL}/signup/usernamecheck`, { username })
.then((res) => {
dispatch(authError(res.data.error));
})
.catch((error) => {
console.log(error);
});
};
}, 500);
The problem lies in that the above code no longer fires off my post request like the initial function did and nothing ever gets dispatched. I know I'm doing something wrong but can't figure out what.
EDIT:
This is how I've set up my store
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
Take a look at this:
http://codepen.io/anon/pen/egeOyJ
const userService = _.debounce(username => {
setTimeout(
()=>{
console.log('userService called after debounce. username:', username)
}
,1000)
}, 500)
const uniqueUsernameCheck = (username) => (dispatch) => {
console.log('I can see this')
userService(username)
}
console.log('begin')
const reducers = (action) => {console.log(action)}
const store = Redux.createStore(
reducers,
{},
Redux.applyMiddleware(ReduxThunk.default))
store.dispatch(uniqueUsernameCheck('rafael'))
store.dispatch(uniqueUsernameCheck('rafael'))
store.dispatch(uniqueUsernameCheck('rafael'))

Categories

Resources