Validate unit test logic that runs inside a Promise.resolve - reactjs

Setup
react : 16.6.0
react-native : 0.57.4
jest : 23.6.0
enzyme : 3.5.0
I have the following logic inside a component
onRefresh = () => {
const { getCustomerAccounts } = this.props
this.setState({ refreshing: true })
getCustomerAccounts()
.then(() => this.setState({ refreshing: false }))
};
which I'm trying to test is using jest like so
describe('Instance', () => {
const getCustomerAccountsMock = jest.fn(() => Promise.resolve({}))
const props = {
getCustomerAccounts: getCustomerAccountsMock,
}
const instance = shallow(<Component {...props} />).instance()
describe('onRefresh', () => {
it('should call getCustomerAccounts', () => {
instance.onRefresh()
expect(getCustomerAccountsMock).toHaveBeenCalled()
expect(getCustomerAccountsMock).toHaveBeenCalledTimes(1)
expect(getCustomerAccountsMock.mock.calls[0][0]).toBeUndefined()
})
})
})
test runs fine but I'm not able to test what happens when getCustomerAccounts().then() runs
Basically I want to test does this.state.refreshing get set to false when getCustomerAccounts().then() runs
Suggestions?

Return the Promise from onRefresh:
onRefresh = () => {
const { getCustomerAccounts } = this.props
this.setState({ refreshing: true })
return getCustomerAccounts() // <= return the Promise
.then(() => this.setState({ refreshing: false }))
};
...then you can test it like this:
describe('Instance', () => {
const getCustomerAccountsMock = jest.fn(() => Promise.resolve({}))
const props = {
getCustomerAccounts: getCustomerAccountsMock,
}
const wrapper = shallow(<Component {...props} />)
const instance = wrapper.instance()
describe('onRefresh', () => {
it('should call getCustomerAccounts', async () => { // <= async test function
await instance.onRefresh() // <= await the Promise
expect(getCustomerAccountsMock).toHaveBeenCalled()
expect(getCustomerAccountsMock).toHaveBeenCalledTimes(1)
expect(getCustomerAccountsMock.mock.calls[0][0]).toBeUndefined()
expect(wrapper.state('refreshing')).toBe(false); // Success!
})
})
})
Details
Returning the Promise lets you await it in the test.
Use an async test function so you can await the returned Promise.
Assign the wrapper to a variable so you can use it to check the state.

Related

Unit testing onSuccess for a react query call

I have a component that looks like this:
<TestComponent refetch={fn} />
Within TestComponent, I have a save button that fires off a mutation:
const handleSaveClick = () => {
const reqBody: AddHotelRoomDescription = {...}
addHotelRoomDescriptionMutation(reqBody, {
onSuccess: () => {
closeDrawer();
handleRefetch();
}
})
}
Within my test file for this component, I am trying to ensure handleRefetch gets called:
it('successfully submits and calls the onSuccess method', async () => {
const mockId = '1234'
nock(API_URL)
.post(`/admin/${mockId}`)
.reply(200);
const user = userEvent.setup();
const editorEl = screen.getByTestId('editor-input');
await act( async () => { await user.type(editorEl, 'Updating description'); });
expect(await screen.findByText(/Updating description/)).toBeInTheDocument();
const saveButton = screen.getByText('SAVE');
await act( async () => { await user.click(saveButton) });
expect(mockedMutate).toBeCalledTimes(1);
await waitFor(() => {
expect(mockedHandleRefetch).toBeCalledTimes(1); <-- fails here
})
})
I am not sure how to proceed here. I know I can test useQuery calls by doing:
const { result } = renderHook(() => useAddHotelRoomDescription(), { wrapper }
But I think this is for a different situation.
Appreciate the guidance!

mock api call in a custom hook using react test library

I have written a custom hook and inside it's useEffect function I am calling an API and set the result into state. Here is my custom hook:
export const useGetUsers = (searchParams?: any | undefined, userId?: string) => {
const [users, setUsers] = useState<{
data: readonly any[] | null;
loading: boolean;
}>({
data: [],
loading: true,
});
const parsedSearchParams = {
limit: 100,
...(searchParams || {}),
};
const searchParamStr = `?${makeQueryStringFromObject(parsedSearchParams)}`;
useEffect(() => {
userRequest('users', 'get', null, searchParamStr)
.then(result => {
setUsers({
data: result?.data,
loading: false,
});
})
.catch(() => {
setUsers({
data: null,
loading: false,
});
});
}, [userId, searchParamStr]);
return { users, setUsers };
};
I want my test to get through .then(). but for some reason it does not. here is my test:
test('when the call is a success', async () => {
const spy = jest.spyOn(ES, 'userRequest');
const returnPromise = Promise.resolve({data: ['a']})
ES.userRequest = jest.fn(() => returnPromise);
const { result, waitFor} = renderHook(() => useGetUsers());
await act(() => returnPromise)
await waitFor(() => expect(spy).toHaveBeenCalled())//this fails
});
here is another try and change I made in my test, but no luck:
test('when the call is a success', async () => {
jest.mock('src/..', () => ({
...jest.requireActual('src/..'),
userRequest: jest
.fn()
.mockImplementation(() => new Promise(resolve => resolve({data: ['a']}))),
}));
const { result, waitFor} = renderHook(() => useGetUsers());
await waitFor(() => expect(ES.userRequest).toHaveBeenCalled())
});
P.S. when I mock userRequest, I expect to have the return value as I mocked. but it fails. it goes to .catch instead
I tried to use waitForNextUpdate, but no luck. I would appreciate your help
This works for me:
import { renderHook, waitFor } from '#testing-library/react';
import { useGetUsers } from '../useGetUsers';
import * as utils from '../useGetUsersUtils';
it('should work', async () => {
const mockUserRequest = jest.spyOn(utils, 'userRequest');
renderHook(() => useGetUsers());
await waitFor(() => expect(mockUserRequest).toHaveBeenCalled())
});
I am not sure where is the userRequest placed in your code. As you can see from my import it is in different file then the hook.

Jest - destructure property

export const AppLayout: React.FunctionComponent = React.memo(({ children }) => {
// Application main layout component name
AppLayout.displayName = getComponentName('App-Layout');
const { isAuthenticated } = useAuth();
const { sendRequest } = useApiService();
React.useEffect(() => {
const fetchData = async () => {
try {
...
} catch (err) {
console.error(err);
}
};
isAuthenticated() && fetchData();
}, []);
describe('App General component', () => {
const useAuth = jest.fn();
const useApiService = jest.fn();
const isAuthenticated = true;
const props = {};
const renderComponent = () => render(
<AppLayout/>
);
it('should render without errors', () => {
renderComponent();
});
/**
* Validate current user exist in session
* #returns {boolean}
*/
const isAuthenticated = React.useCallback((): boolean => {
return Boolean(user);
}, [user]);
How can I set isAuthenticated to true so I can avoid the error
TypeError: Cannot destructure property 'isAuthenticated' of
const mockUseAuthIsAuthenticated = jest.fn(() => false);
const mockUseAuth = jest.fn(() => ({
isAuthenticated: mockUseAuthIsAuthenticated,
});
jest.mock("../hooks/useAuth", mockUseAuth);
describe('My test case', () => {
it(`should return authenticated=TRUE`, () => {
// Given
mockUseAuthIsAuthenticated.mockImplementationOnce(
() => true
);
// When
// assuming `render` comes from the react testing-library
render(<ComponentThatCallsTheHook />);
// Then
expect(mockUseAuthIsAuthenticated).toHaveBeenCalledOnce();
// ... more expectations
});
});
You should mock the useAuth hook like this:
jest.mock("yourUseAuthPath", () => ({
useAuth: () => ({
isAuthenticated: () => true
}),
}));
describe('App General component', () => {
...
}
n.b. You should replace the yourUseAuthPath with the correct path where you get the useAuth from. Example:
import { useAuth } from "yourUseAuthPath";
Some official docs here: https://jestjs.io/docs/mock-functions#mocking-partials

fireEvent.click(button) causing error **Call retries were exceeded at ChildProcessWorker.initialize**

One of my unit tests is failing when I'm trying to fire a click event on a component. The component is being rendered and is enabled.
//component
import {makeEncryptedCall} from '../../foo';
const MyComponent = (props) => {
const onRedirection = async () => {
const param = {foo: 'bar'};
return await c(param)
.then((data) => {
history.push('/some-url');
});
};
return (
<>
<button
onClick={onRedirection}
data-testid='my-button'
/>
</>
)
}
// test
it('should fire redirection flow', () => {
jest.mock('../../foo', () => {
return {
makeEncryptedCall: jest.fn(() => {
const response = {
ok: true,
json: () => {
Promise.resolve({
data: 'superEncryptedStuff';
});
}
};
return Promise.resolve(response);
});
}
});
const component = screen.getByTestId('my-button');
expect(component).toBeEnabled();
fireEvent.click(component);
});
I tried finding solutions related to Call retries were exceeded posted before but they are related to setTimeouts, FakeTimers, or async-mock(which I have already implemented).
Note: The test passes when I comment out fireEvent.click. Test only fails when the event is triggered.
The issue was resolved by wrapping the fireEvent in a waitFor function.
it('should fire redirection flow', async () => {
jest.mock('../../foo', () => {
return {
makeEncryptedCall: jest.fn(() => {
const response = {
ok: true,
json: () => {
Promise.resolve({
data: 'superEncryptedStuff';
});
}
};
return Promise.resolve(response);
});
}
});
const component = screen.getByTestId('my-button');
expect(component).toBeEnabled();
await waitFor(() => fireEvent.click(component));
});

Test react forceUpdate custom hook useEffect/useState

I created a custom hook to force a component to update but I'm having issues figuring out how to write a unit test with jest.
This is the hook
function useForceUpdate(condition) {
const [, setState] = useState(0);
const forceUpdate = () => setState(1);
useEffect(() => {
if (condition) {
forceUpdate();
}
}, [condition]);
}
export default useForceUpdate;
I was able to successfully test this hook this way
import React from "react";
import useForceUpdate from "hooks/use-force-update";
const Component = ({ shouldUpdate }) => {
const hasUpdated = useForceUpdate(shouldUpdate);
return <div>{hasUpdated}</div>;
};
describe("useForceUpdate", () => {
let subject;
let props;
beforeEach(() => {
props = { shouldUpdate: true };
subject = memoize(() => mount(<Component {...props} />));
});
describe("when the condition is true", () => {
it("it calls forceUpdate", () => {
expect(
subject()
.find("div")
.text()
).toBe("1");
});
});
describe("when the condition is false", () => {
beforeEach(() => {
props = { shouldUpdate: false };
});
it("it does not call forceUpdate", () => {
expect(
subject()
.find("div")
.text()
).toBe("0");
});
});
});

Resources