I have some code which works. However for my test I would like to mock the fetch that is done in the component.
The test
I am trying the following:
import ConnectedComponent from './Component';
import { render } from '#testing-library/react';
import user from '../__models__/user'; // arbitrary file for the response
// create a mock response
const mockSuccessResponse = user;
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
// Trying mock the refetch from http
jest.mock('./http', () => {
return {
refetch: () => ({
settingsFetch: () => mockFetchPromise,
})
}
});
it('renders', async () => {
const { getByText } = render(Component);
const title = await getByText('My title');
expect(title).toBeInTheDocument();
});
Error message
With this I receive the following error:
● Test suite failed to run
TypeError: (0 , _http.refetch)(...) is not a function
The Application code
This code is working fine in my application. To give you an example:
./http.js
import { connect } from 'react-refetch';
export async function fetchWithToken(urlOrRequest, options = {}) {
// some stuff
return response;
}
export const refetch = connect.defaults({
fetch: fetchWithToken,
});
./Component.jsx
import { refetch } from './http';
const Component = ({ settingsFetch }) => <AnotherComponent settingsFetch={settingsFetch} />);
const ConnectedComponent = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'http://some-url/api/v1/foo'
}
})
)(Component)
export default ConnectedComponent;
How can I mock this function to return a mocked Promise as the response?
Update: It's getting close by doing the following:
jest.mock('../helpers/http', () => ({
refetch: () => jest.fn(
(ReactComponent) => (ReactComponent),
),
}));
Now the error reads:
Warning: Failed prop type: The prop `settingsFetch` is marked as required in `ConnectedComponent`, but its value is `undefined`.
Which means I will probably have to provide the mocked responses for the fetches in there somewhere.
Jest itself is in charge of the modules. So in the following example you will see that the module coming from '../http' can be mocked.
You can then overwrite the props of that module by first adding the default props, and after that overwrite the ones you need with your own.
jest.mock('../http', () => {
return {
refetch: function(hocConf) {
return function(component) {
component.defaultProps = {
...component.defaultProps,
settingsFetch: {},
// remember to add a Promise instead of an empty object here
};
return component;
};
},
};
});
Related
I have a following useQuery hook usage:
const { data: user, refetch } = useQuery({
queryKey: ['user', userId],
queryFn: () =>
UserApi.getUserById(userId)
.then(({ data }) => data)
.catch(() => {
NotificationManager.error('Error with getting user with id: ' + userId, { icon: true });
})
});
This works fine in my component, but I have problems with tests:
import React from 'react';
import { screen } from '#testing-library/react';
import '#testing-library/jest-dom';
import { UserApi } from '../../api';
import { render } from '../../config/test/root-render';
import useCurrentUserInfo from '../../hooks/useCurrentUserInfo';
import UserDetails from './UserDetails';
jest.mock('../../api/UserApi');
jest.mock('../../hooks/useCurrentUserInfo');
jest.mock('../../root/router', () => {
return {
useCurrentRoute: () => ({ params: { userId: 'userId' }, parent: { key: '' } }),
generateUrlByKey: () => '1'
};
});
function mockApi(userFiles, userStatus, userSecondaryStatus) {
UserApi.getUserById = jest.fn().mockReturnValue(
new Promise((resolve) =>
resolve({
data: { userFiles: userFiles, general: { status: userSecondaryStatus}, userStatus: userStatus}
})
)
);
}
function mockAndRender(userFiles, userStatus, userSecondaryStatus) {
useCurrentUserInfo.mockReturnValue([{}]);
mockApi(userFiles, userStatus, userSecondaryStatus);
render(<UserDetails />);
}
describe('<UserDetails />', () => {
it('test', () => {
mockAndRender([1], 'Awarded', null)
})
})
And as you can see I didn't mock useQuery hook, cause I don't need it. I have to mock api call instead of hook. Moreover, mock of my api call works as expected (I've checked and verified it with debugger), but useQuery is returning undefined. Does anybody has ideas how to fix it?
So, after deep research, I've found the root of the problem. The code described above is correct. But the problem was in lines I did not provide in my question.
expect(screen.getByTestId('username')).toBeInTheDocument();
Method getByTestId is getting data faster than data is defined. After changing the code to this it works as expected:
expect(await screen.findByTestId('username')).toBeInTheDocument();
I am working on a React Native application and am very new to testing. I am trying to mock a hook that returns a true or false boolean based on the current user state. I need to mock the return value of the authState variable, and based on that, I should check if the component is rendered or not. But the jest mock is returning the same value only
useAuth.ts
export const useAuthState = () => {
const [authState, setAuthState] = useState<AuthState>();
useEffect(() => {
return authentication.subscribe(setAuthState);
}, []);
return authState;
};
MyComponent.tsx
export const MyComponent = () => {
const authState = useAuthState();
if (!authState) {
return null;
}
return <AnotherComponent />
}
MyComponent.test.tsx
import { MyComponent } from "./MyComponent"
jest.mock('../use-auth-state', () => {
return {
useAuthState: () => false,
};
});
const TestComponent = () => <MyComponent />
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
let testRenderer: ReactTestRenderer;
act(() => {
testRenderer = create(<TestComponent />);
});
const testInstance = testRenderer.getInstance();
expect(testInstance).toBeNull()
})
})
This is working fine. But, I am not able to mock useAuthState to be true as this false test case is failing. Am I doing it right? I feel like I am messing up something.
You want to change how useAuthState is mocked between tests, right? You can set your mock up as a spy instead and change the mock implementation between tests.
It's also a little more ergonomic to use the render method from react-testing-library. The easiest way would be to give your component a test ID and query for it. Something like the below
import { MyComponent } from "./MyComponent"
import * as useAuthState from '../use-auth-state';
const authStateSpy = jest.spyOn(useAuthState, 'default');
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
// you can use .mockImplementation at any time to change the mock behavior
authStateSpy.mockImplementation(() => false);
const { queryByTestId } = render(<MyComponent />;
expect(queryByTestId('testID')).toBeNull();
})
I need to test the following component that consumes a custom hook of mine.
import { useMyHook } from 'hooks/useMyHook';
const MyComponent = () => {
const myHookObj = useMyHook();
const handler = () => {
myHookObj.myMethod(someValue)
}
return(
<button onClick={handler}>MyButton</button>
);
};
This is my test file:
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: () => {
return {
myMethod: jest.fn(),
};
},
};
});
describe('<MyComponent />', () => {
it('calls the hook method when button is clicked', async () => {
render(<MyComponent {...props} />);
const button = screen.getByText('MyButton');
userEvent.click(button);
// Here I need to check that the `useMyHook.method`
// was called with some `value`
// How can I do this?
});
});
I need to check that the useMyHook.method was called with some value.
I also want to test it from multiple it cases and it might be called with different values on each test.
How can I do this?
This is how I was able to do it:
import { useMyHook } from 'hooks/useMyHook';
// Mock custom hook that it's used by the component
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: jest.fn(),
};
});
// Mock the implementation of the `myMethod` method of the hook
// that is used by the Component
const myMethod = jest.fn();
(useMyHook as ReturnType<typeof jest.fn>).mockImplementation(() => {
return {
myMethod: myMethod,
};
});
// Reset mock state before each test
// Note: is needs to reset the mock call count
beforeEach(() => {
myMethod.mockReset();
});
Then, on the it clauses, I'm able to:
it (`does whatever`, async () => {
expect(myMethod).toHaveBeenCalledTimes(1);
expect(myMethod).toHaveBeenLastCalledWith(someValue);
});
I'm trying to write a test for the following:
import React from 'react'
import Popup from 'some-library'
const popupConfig = {
home: {
popupValue: 'Hello World',
popupValue: 'action',
popupMessage: 'Get Started'
},
settings: {
popupValue: 'Hello World',
popupValue: 'action',
popupMessage: 'Get Started'
}
}
const closePopup = () => {
Popup.closePopup()
}
const toggleNewPopup = () => {
Popup.togglePopup('some-popup')
}
const GetStartedPopup = ({ moduleName }) => {
if (!Object.keys(popupConfig).includes(moduleName)) return null
const {
popupValue = 'Hi there!',
popupStyle = 'warning',
popupMessage = 'Get Started',
popupBtnFunction = toggleNewPopup
} = popupConfig[moduleName]
return (
<Popup
popupValue={popupValue}
popupStyle={popupStyle}
popupBtnValue={popupMessage}
popupBtnStyle="neutral"
popupBtnFunction={popupBtnFunction}
xPopup={closePopup}
/>
)
}
export default GetStartedPopup
The objective of the test is to make sure that the closePopup and toggleNewPopup functions are called. I'm doing the following to do that for the closePopup function:
import React from 'react'
import { mount } from 'enzyme'
import { Popup } from 'some-library'
import GetStartedPopup from 'widgets/getStartedPopup'
describe('<GetStartedPopup/>', () => {
let wrapper
let props
beforeEach(() => {
props = {
page: 'home'
}
wrapper = mount(<GetStartedPopup {...props}/>)
})
it('should render the component without crashing', () => {
expect(wrapper).toBeDefined();
})
it('should call closePopup', () => {
const spy = jest.spyOn(wrapper.instance(), 'closePopup');
wrapper.instance().closePopup();
expect(spy).toHaveBeenCalledTimes(1);
})
afterEach(() => {
wrapper.unmount()
})
})
I went through the docs for spyOn and other SO threads that tackle issues like this but couldn't resolve how to test the closePopup and toggleNewPopup functions for my case here. When I run the test case written above I get this: TypeError: Cannot read property 'closePopup' of null. What would be the correct way to write the test to make sure that the two functions are called?
Funny that I ran into this myself at work in regards to wrapper.instance() doc
To return the props for the entire React component, use wrapper.instance().props. This is valid for stateful or stateless components in React 15.. But, wrapper.instance() will return null for stateless React component in React 16., so wrapper.instance().props will cause an error in this case.
As for the 3rd party library. You should be mocking any collaborators that your component uses.
import { Popup } from 'some-library';
describe('<GetStartedPopup />', () => {
let wrapper;
jest.mock('some-library', () => {
Popup: jest.fn(),
});
const initialProps = {
page: 'home'
};
const getStartedPopup = () => {
return mount(<GetStartedPopup {...initialProps});
};
beforeEach(() => {
Popup.mockClear()
wrapper = getStartedPopup();
};
it('should call closePopup', () => {
expect(Popup.closePopup()).toHaveBeenCalledTimes(1);
});
...
});
can anyone tell me how to wait in jest for a mocked promise to resolve when mounting a component that calls componendDidMount()?
class Something extends React.Component {
state = {
res: null,
};
componentDidMount() {
API.get().then(res => this.setState({ res }));
}
render() {
if (!!this.state.res) return
return <span>user: ${this.state.res.user}</span>;
}
}
the API.get() is mocked in my jest test
data = [
'user': 1,
'name': 'bob'
];
function mockPromiseResolution(response) {
return new Promise((resolve, reject) => {
process.nextTick(
resolve(response)
);
});
}
const API = {
get: () => mockPromiseResolution(data),
};
Then my testing file:
import { API } from 'api';
import { API as mockAPI } from '__mocks/api';
API.get = jest.fn().mockImplementation(mockAPI.get);
describe('Something Component', () => {
it('renders after data loads', () => {
const wrapper = mount(<Something />);
expect(mountToJson(wrapper)).toMatchSnapshot();
// here is where I dont know how to wait to do the expect until the mock promise does its nextTick and resolves
});
});
The issue is that I the expect(mountToJson(wrapper)) is returning null because the mocked api call and lifecycle methods of <Something /> haven't gone through yet.
Jest has mocks to fake time travelling, to use it in your case, I guess you can change your code in the following style:
import { API } from 'api';
import { API as mockAPI } from '__mocks/api';
API.get = jest.fn().mockImplementation(mockAPI.get);
jest.useFakeTimers(); // this statement makes sure you use fake timers
describe('Something Component', () => {
it('renders after data loads', () => {
const wrapper = mount(<Something />);
// skip forward to a certain time
jest.runTimersToTime(1);
expect(mountToJson(wrapper)).toMatchSnapshot();
});
});
Alternatively to jest.runTimersToTime() you could also use jest.runAllTimers()
As a workaround convert it from async to sync
jest.spyOn(Api, 'get').mockReturnValue({
then: fn => fn('hello');
});