Jest simple async await with timer not working - timer

I'm trying to get a simple async/await test working with a setTimeout but nothing is happening when I run it:
const testing = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result');
}, 500);
});
}
jest.useFakeTimers()
it('tests async await', async () => {
const r = await testing();
expect(r).toBe('result');
jest.runAllTimers();
});
I'd be fine with using real setTimeout like in Jasmine but it seems in Jest you have to use fake ones. So I did include jest.useFakeTimers() and jest.runAllTimers() but that didn't solve it.
The test gets stuck and never completes. Any idea what could be the issue?

Try following:
it('tests async await', async () => {
jest.useFakeTimers();
testing = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result');
}, 500);
});
};
const asyncResult = testing();
jest.runAllTimers();
const r = await asyncResult;
expect(r).toBe('result');
});

Related

Same function for all queries onSuccess react-query

I have a use-case where I would like to run the same function onSuccess for all mutations and queries globally instead of having to set the same function on each individual query (i have a lot of queries)
I have a bunch of queries like so
const q1 = useQuery(
"q1",
async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
},
{
onSuccess: () => generic(),
}
);
const q2 = useQuery(
"q2",
async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
},
{
onSuccess: () => generic(),
}
);
const q1 = useQuery(
"q3",
async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
},
{
onSuccess: () => generic()
}
);
function generic() {
return "should be set globally and run on ever OnSuccess event"
}
However, I would like to set this globally for all quires, something like this
const queryCache = new QueryClient({
defaultConfig: {
queries: {
onSuccess: () => {
return "should be set globally and run on ever OnSuccess event";
},
},
},
});
const q1 = useQuery("q1", async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
});
const q2 = useQuery("q2", async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
});
const q1 = useQuery("q3", async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
});
I have searched the docs for about an hour for this type of functionality but cannot find anything
I was able to find out how to solve this for my use-case, it was a case of setting the OnSuccess function using setDefaultOptions.
turns out this code
const queryCache = new QueryClient({
defaultConfig: {
queries: {
onSuccess: () => {
return "should be set globally and run on ever OnSuccess event";
},
},
},
});
wasn't doing anything, instead i set the defaults through the function
const queryCache = new QueryClient();
queryCache.setDefaultOptions({
queries: {
refetchOnWindowFocus: false,
onSuccess: () => console.log("Got IM!"),
},
});
This triggers console.log("Got Im!") onSuccess for every time i call my API which is the desired outcome for my use-case.
i can see that const queryCache = new QueryClient(); does have a constructor that takes defaultOptions however, for whatever reason they do not set.
EDIT
turns out it does work passing it to the constructor, its just this code was written when using an older version of react-query when the key was defaultConfig instead of defaultOptions. This code also works (aswell as the solution above)
const queryCache = new QueryClient({
defaultOptions: {
queries: {
onSuccess: () => console.log("Got IM!"),
},
},
});
There is an open PR for that exact use case: https://github.com/tannerlinsley/react-query/pull/2404
It adds the possibility to have a global onSuccess callback on the queryCache.

How to fire an event React Testing Library

I have some code, in a hook, to detect whether the browser is online / offline:
export function useConnectivity() {
const [isOnline, setNetwork] = useState(window.navigator.onLine);
const updateNetwork = () => {
setNetwork(window.navigator.onLine);
};
useEffect(() => {
window.addEventListener('offline', updateNetwork);
window.addEventListener('online', updateNetwork);
return () => {
window.removeEventListener('offline', updateNetwork);
window.removeEventListener('online', updateNetwork);
};
});
return isOnline;
}
I have this basic test:
test('hook should detect offline state', () => {
let internetState = jest.spyOn(window.navigator, 'onLine', 'get');
internetState.mockReturnValue(false);
const { result } = renderHook(() => useConnectivity());
expect(result.current.valueOf()).toBe(false);
});
However, I want to run a test to see whether it returns the correct value when an offline event is triggered, not just after the mocking of the returned value on render. What is the best way to approach this? Where I have got so far is this:
test('hook should detect offline state then online state', async () => {
const { result, waitForNextUpdate } = renderHook(() => useConnectivity());
act(() => {
const goOffline = new window.Event('offline');
window.dispatchEvent(goOffline);
});
await waitForNextUpdate();
expect(result.current).toBe(false);
});
I'm not sure about 'best', but this is one way: change the mock response halfway through the test, and tweak some of the async code:
test('hook should detect online state then offline state', async () => {
const onLineSpy = jest.spyOn(window.navigator, 'onLine', 'get');
// Pretend we're initially online:
onLineSpy.mockReturnValue(true);
const { result, waitForNextUpdate } = renderHook(() => useConnectivity());
await act(async () => {
const goOffline = new window.Event('offline');
// Pretend we're offline:
onLineSpy.mockReturnValue(false);
window.dispatchEvent(goOffline);
await waitForNextUpdate();
});
expect(result.current).toBe(false);
});

How to reset a spy or mock in jest

I have a function that I have mocked in my test cases file.
MyService.getConfigsForEntity = jest.fn().mockReturnValue(
new Promise(resolve => {
let response = [];
resolve(response);
})
);
Now I want all my test cases in that file to use this mock like
describe('Should render Component Page', () => {
it('should call the API', async () => {
const {container} = render(<MyComp entityName='entity1'/>);
await wait(() => expect(MyService.getConfigsForEntity).toHaveBeenCalled());
});
});
The only issue is in only one of the test case I want to mock the return value differently.
But all other test cases before and after can use the global mock.
describe('Should call API on link click', () => {
it('should call the API on link click', async () => {
const spy = jest.spyOn(MyService, 'getConfigsForEntity ').mockReturnValue(
new Promise(resolve => {
let response = [{
"itemName": "Dummy"
}];
resolve(response);
});
const {container} = render(<MyComp entityName='entity1'/>);
await wait(() => expect(spy).toHaveBeenCalled());
spy.mockClear();
});
});
The problem is , once I mock the function differently inside one test case , all other test cases after that test case, that are using the global mock , are failing,
But it only works if I put the test case after all other test cases.
What am I doing wrong?
You can try with mockRestore():
beforeEach(() => {
spy.mockRestore();
});
have you tried?
beforeEach(() => {
jest.clearAllMocks();
})

Clean up async function in an useEffect React hook

I have the following useEffect function and trying to find the best way to clean this up when the component unmounts.
I thought it would be best to follow the makeCancelable from the React docs, however, the code still executes when the promise is cancelled.
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
//example useEffect
useEffect(() => {
const getData = async () => {
const collectionRef_1 = await firestore.collection(...)
const collectionRef_2 = await firestore.collection(...)
if (collectionRef_1.exists) {
//update local state
//this still runs!
}
if (collectionRef_2.exists) {
//update local state
//and do does this!
}
}
const getDataPromise = makeCancelable(new Promise(getData))
getDataPromise.promise.then(() => setDataLoaded(true))
return () => getDataPromise.cancel()
}, [dataLoaded, firestore])
I have also tried const getDataPromise = makeCancelable(getData) without any luck. The code executes fine, just doesn't clean up correctly when the component unmounts.
Do I need to also cancel the two await functions?
In your makeCancelable function you are just checking the value of hasCanceled_ after the promise has finished (meaning getData has already executed entirely):
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
// AFTER PROMISE RESOLVES (see following '.then()'!), check if the
// react element has unmount (meaning the cancel function was called).
// If so, just reject it
promise.then(
val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
Instead, in this case I would recomend you to go for a simpler and more classic solution and use a isMounted variable to create the logic you want:
useEffect(() => {
let isMounted = true
const getData = async () => {
const collectionRef_1 = await firestore.collection(...)
const collectionRef_2 = await firestore.collection(...)
if (collectionRef_1.exists && isMounted) {
// this should not run if not mounted
}
if (collectionRef_2.exists && isMounted) {
// this should not run if not mounted
}
}
getData().then(() => setDataLoaded(true))
return () => {
isMounted = false
}
}, [dataLoaded, firestore])

How do I properly test for a rejected promise using Jest?

Code
import { createUser } from '../services';
...
...
handleFormSubmit = () => {
this.setState({ loading: true });
createUser()
.then(() => {
this.setState({
loading: false,
});
})
.catch(e => {
this.setState({
error: e,
});
});
};
Test
it('rejects...', () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
return wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
});
});
Mock
export const createUser = function() {
return new Promise((resolve, reject) => {
reject('error');
});
};
The test does force the code to go into the catch in the method. So the state does get set to 'error'.
But in my test, it doesn't do what I expect and wait for the Promise to reject before it tests for the state change.
I'm not sure what to try here, should I be using async/await?
So it's the createUser method I want to wait for but I'm not sure my implementation allows for this.
You should do something like this:
it('rejects...', () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
return expect(wrapper.instance().handleFormSubmit()).rejects.toEqual('error');
});
I think it is cleaner this way. You can see this approach in the official docs.
It's important to note that .rejects (and .resolves) returns a promise, which is returned in the example above so that jest knows to wait on it. If you don't return it, you MUST await it:
it('rejects...', async () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
await expect(wrapper.instance().handleFormSubmit()).rejects.toEqual('error');
});
The test fails because it's not aware that the subject is asynchronous. It can be fixed by using a done param or making the test function async.
Note it's also necessary to set the number of expected assertions so that the test will fail even if the catch branch is not taken.
async/await style:
it('rejects...', async () => {
expect.assertions(1);
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
await wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
});
});
Older style done param:
it('rejects...', done => {
expect.assertions(1);
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
done();
});
});
Asynchronous Testing Reference
expect.assertions reference
Your code looks correct. Why do you say that it doesn't wait for the Promise to reject? The only difference I would make would be to make use of Jest's mocking capability, so change
Mock
export const createUser = function() {
return new Promise((resolve, reject) => {
reject('error');
});
};
to
Test
jest.mock('../services');
const services = require('../services');
const createUser = jest.spyOn(services, "createUser");
createUser.mockRejectedValue("error");
...
it('rejects...', () => {
There's no need to have a separate Mock file
In your code handleFormSubmit function should return Promise on which you can wait in your test. Also you need to return truthful data from success and error callback to resolve and reject the promise respectively.
handleFormSubmit = () => {
this.setState({ loading: true });
return createUser()
.then(() => {
this.setState({
loading: false,
});
return true;
})
.catch(e => {
this.setState({
error: e,
});
throw e;
});
};
Here in your actual code you have caught the error in catch handler and trying to catch it further in out test case code. Hence catch can not be chained further, while you can chain then multiple times.
For reference go through Promise documentations:
https://www.peterbe.com/plog/chainable-catches-in-a-promise

Resources