mock useDispatch followed by .then() with jest inside functional component - reactjs

My scenario is just one step ahead of this existing question in stackoverflow
I have dispatch fn but with .then() followed by.
Component:
const Testing = props => {
const [counterData, setCounterData] = useState(0)
const resData = useSelector(state => state.test)
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchTestinData())
.then(res => {
console.log(res);
setCounterData(prev => prev + 1)
})
}, [])
return <div>
Testing component
<div>
Result - {resData.title}
</div>
<div>
Counter - {counterData}
</div>
</div>
}
Test file:
// const mockDispatch = jest.fn().mockResolvedValueOnce({json : async () => []})
const mockDispatch = jest.fn().mockImplementation(() =>
Priomise.resolve({title:'tets'}))
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => mockDispatch
}))
describe('<Testing />', function () {
const getComponent = (state) => <Provider store={store}>
<Testing />
</Provider>
it('testing success api', () => {
// window.fetch = jest.fn().mockResolvedValueOnce({title: 'testing title'})
render(getComponent())
screen.debug()
// expect(mockDispatch).toBeCalledTimes(1)
})
})
if am using just jest.fn() getting same error as well as with mock implemntaion.
Error screenshot
Something am missing in mock fn implementaion.
Plase help. Searched a lot but no luck.

Apparently Jest docs are a bit misleading about the possibility to use previously defined variables in a mock module factory: that is just not possible.
So the solution to your issue is just to move your mockDispatch implementation inside the module factory:
jest.mock('react-redux',
() => ({
...jest.requireActual('react-redux'),
useDispatch: () => jest.fn().mockImplementation(() =>
Promise.resolve({ title: 'test' }))
})
)

Related

react native Jest mock useColorScheme

I am trying to mock the useColorScheme hook from react native so I can control what values it returns. My code is below:
const mockColorScheme = jest.fn();
jest.mock('react-native/Libraries/Utilities/useColorScheme', () => ({
useColorScheme: mockColorScheme,
}));
it('Renders correct theme when user selects light', () => {
const wrapper = ({children}: any) => (
<ThemeProvider userIsUsingDarkMode={false} userIsUsingSystemTheme={false}>
{children}
</ThemeProvider>
);
const {result} = renderHook(() => useTheme(), {wrapper});
expect(result.current.theme).toBeDefined();
expect(result.current.theme?.text).toStrictEqual('#333');
mockColorScheme.mockImplementationOnce(() => 'dark');
expect(result.current.theme).toBeDefined();
expect(result.current.theme?.text).toStrictEqual('#fbfbfb');
});
I would expect this to work, but I get the following error:
TypeError: (0 , _reactNative.useColorScheme) is not a function
This comes from my ThemeProvider component:
export const ThemeProvider: FunctionComponent<ThemeProviderProps> = ({
children,
userIsUsingDarkMode,
userIsUsingSystemTheme,
}) => {
const isDarkMode = useColorScheme() === 'dark';
...
export const useTheme = () => {
return useContext(ThemeContext);
};
If anyone has any ideas as to how to mock this I would really appreciate it. Thank you.
I was struggled for hours to solve the same problem, and I think I found a solution.
All you have to do is to mock actual module for the hook.
const mockedColorScheme = jest.fn()
jest.mock("react-native/Libraries/Utilities/useColorScheme", ()=> ({
...jest.requireActual("react-native/Libraries/Utilities/useColorScheme"),
useColorScheme: mockedColorScheme
}))
it("renders useColorScheme hook with return value of 'dark'", () => {
mockedColorScheme.mockImplementationOnce(() => "dark")
const { result } = renderHook(() => mockedColorScheme())
expect(result.current).toBeDefined()
expect(result.current).toEqual("dark")
})
We could simply mock the module default export.
const mockedUseColorScheme = jest.fn();
jest.mock('react-native/Libraries/Utilities/useColorScheme', () => {
return {
default: mockedUseColorScheme,
};
});

ReactJS - Test conditional rendering in component

I have a component the uses useEffect to fetch data from a file.
In the component i have a condiiton that only shows the content of the component if we have data.
Now how can a test the conditional part of the content i my test case?
This is what i have right now:
Component:
function MunicipalityInfo() {
const [municipalityData, setMunicipalityData] = useState({})
const fetchData = async () => {
try{
const result = await fetch(XMLFile)
const data = await result.text();
const xml = new XMLParser().parseFromString(data);
const res = XMLMapper(xml)
setMunicipalityData(res)
}catch(e){
console.log(e)
}
}
useEffect(() => {
fetchData();
}, []);
return(
<>
{ municipalityData.units &&
municipalityData.units.map((city, index) => {
return (
<Div key={index} data-testid="municipalityInfo-component" className="mt-5 p-3">
<HeaderMain data-testid="header-main">{city.City}</HeaderMain>
<HeaderSub data-testid="header-sub" className="mt-4">{city.venamn}</HeaderSub>
<BodyText data-testid="body-text">{city.Address}, {city.City}</BodyText>
<MapLink href={"#"} data-testid="map-link"><i data-testid="map-icon" className="fas fa-map-marker-alt"></i> Show on map</MapLink>
<LinkList data-testid="link-list">
<LinkListItem data-testid="list-item-first">
<Link href={city.BookingURL} data-testid="link-book-vaccination">Some text</Link>
</LinkListItem>
</LinkList>
<Calendar data={city.unit}/>
</Div>
)
})
}
<CitiesSideBar>
<Sidebar data={municipalityData.cities}/>
</CitiesSideBar>
</>
)
}
export default MunicipalityInfo;
And this is my test:
describe("<MunicipalityInfo />", () => {
it("renders without crashing", async () => {
const {queryByTestId, findByText, findByTestId} = render(<MunicipalityInfo/>, {})
expect(queryByTestId("municipalityInfo-component")).not.toBeInTheDocument();
expect(await findByTestId("municipalityInfo-component")).toBeInTheDocument(); <--- this line fails
})
})
And the error of my testcase:
TestingLibraryElementError: Unable to find an element by: [data-testid="municipalityInfo-component"]
if your problem is trying to test if something shouldn't be in the page...
use the queryBy
if you're want it to wait for something... then you want to await findBy (or wrap in a waitFor)
here's the docs: https://testing-library.com/docs/react-testing-library/cheatsheet/
I'm assuming you're mocking the fetch request so it wouldn't be the test problem...
if you're not mocking it... then you probably should mock and return either data or no data to test if it should or not render.
one way to elegantly "avoid" mocking would be by abstracting it in a custom hook:
function useCustomHook(){
const [municipalityData, setMunicipalityData] = useState({})
useEffect(() => {
fetch(XMLData)
.then((res) => res.text())
.then(async (data) => {
let xml = new XMLParser().parseFromString(data);
let result = await XMLMapper(xml)
setMunicipalityData(await result)
})
.catch((err) => console.log(err));
}, []);
return municipalityData;
}
function MunicipalityInfo({municipalityData = useCustomHook()}) { ... }
then in the test you can simply
render(<MunicipalityInfo municipalityData={'either null or some mocked data'} />)

Struggling to mock custom react hook

I'm wondering if anyone can help me with where I'm going wrong here...
Trying to mock a custom hook but jest/enzyme is not recording any calls to the function.
My test:
const mockHandleEditProtection = jest.fn();
const mockUseEditProtection = jest.fn(() => [null, mockHandleEditProtection]);
jest.mock('../../common/useEditProtection', () => ({
__esModule: true,
default: () => mockUseEditProtection,
}));
describe('my test', () => {
const wrapper = mount(<AComponent proposal={mockProposalDataWithEnrichedValues}/>)
it('should call useEditProtection with proposal object', () => {
expect(mockUseEditProtection).toBeCalledWith(mockProposalDataWithEnrichedValues);
});
it('should call edit protection when edit button is clicked', () => {
wrapper.find(TableCell).at(4).find(Button).at(0).simulate('click');
expect(mockHandleEditProtection).toBeCalled();
});
})
A basic example of how the hook is used in a component...
const AComponent = ({proposal}) => {
const [EditWarning, editProtection] = useEditProtection(proposal);
return <div>
{EditWarning}
<button onClick={editProtection}>Edit</button>
</div>
};
I am very confused as to why it isn't working so any pointers are appreciated!

Enzyme/Jest test for useEffect()

I have a component that looks like:
const PersonalGreeting = (): ReactElement => {
const [date, setDate] = useState(new Date())
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date())
}, 60000)
return () => {
clearInterval(timer)
}
}, [])
return (
<div>{date}></div>
)
}
I am getting into an error that states Jest: "global" coverage threshold for functions (89%) not met: 88.73% and when I look at the logs, it says specifically my return () and clearInterval(timer) in useEffect() are not being tested. Here is a screenshot:
I honestly am pulling my hair out trying to figure out what they want me to test. Here is what I have tested:
describe('PersonalGreeting', () => {
const setTimeOfDay = jest.fn()
// eslint-disable-next-line
const useToggleMock: any = (initialState: string) => [
initialState,
setTimeOfDay
]
beforeEach(() => {
jest.spyOn(React, 'useState').mockImplementation(useToggleMock)
})
afterEach(() => {
jest.clearAllTimers()
})
it('renders component as expected', () => {
const wrapper = mount(
<TestWrapper>
<PersonalGreeting />
</TestWrapper>
)
expect(wrapper.text().length > 0).toBe(true)
})
it('Checks time every minute', () => {
jest.useFakeTimers()
mount(
<TestWrapper>
<PersonalGreeting />
</TestWrapper>
)
expect(setTimeOfDay).not.toBeCalled()
// 1 minute
jest.advanceTimersByTime(60000)
expect(setTimeOfDay).toHaveBeenCalledTimes(1)
})
})
You need to unmount your component, this will fire the return function in your useEffect()
See docs
Something like this
it('Should unmount component', () => {
const wrapper = mount(
<TestWrapper>
<PersonalGreeting />
</TestWrapper>
)
wrapper.unmount();
// continue with your expect here
})

Why does the 'then' part in async test fail in jest?

Component:
export const fetchList = () => {
return API.get(AMPLIFY_ENPOINTS.default, API_URLS.list, { response: true });
}
const List: React.FC = () => {
const dispatch = useDispatch();
const setError = useError();
useEffect(() => {
fetchList()
.then((response) => {
if (response && response.data?.length) {
dispatch(setList(response.data));
}
})
.catch((error) => {
setError(error);
});
}, [])
}
Test:
it('should fetch list', async () => {
const wrapper = mount(
<Provider store={store}>
<List />
</Provider>
);
API.get = jest.fn().mockImplementation(() => Promise.resolve({ data: mockList }));
const response = await fetchList();
console.log(store.getActions(), response); // HERE IS THE ISSUE
});
So the store.getActions() returns setError from catch block, why is that? It should return setList from then block. What am I doing wrong? response variable returns mockList just fine.
Edit
The error it returns is API not configured, I'm using aws amplify.
fetchList is called when the component is mounted, mocked API.get doesn't affect the first time it's called, and second call doesn't do anything. It's a bad practice to mock methods by assigning a spy to them because they cannot be restored after a test.
The problem with fetchList is that it cannot be spied or mocked because it's used in the same module it's defined. The promise it creates in useEffect cannot be chained, promises need to be flushed in order to avoid race condition.
It can be:
let flushPromises = () => new Promise(resolve => setImmediate(resolve));
jest.spyOn(API, 'get').mockResolvedValue({ data: mockList });
const wrapper = mount(
<Provider store={store}>
<List />
</Provider>
);
await flushPromises();
expect(store.getActions())...

Resources