Test dispatch action using react testing-library - reactjs

Hi I have a function as shown below:
MyComponent.js-------
const somefunction = () => {
if (someCondition) {
dispatch(someActionMethod({
id: 01,
empName: 'Adam'
}).then((result) => {
setSomeState(result.data);
})
)
}
}
Tried a lot of ways to mock someActionMethod with dispatch, but all efforts are failing.
My test file:
import React from 'react';
import * as redux from 'react-redux';
import { render } from '#testing-library/react';
const useDispatchSpy = jest.spyOn(reactRedux, 'useDispatch');
const mockDispatchFn = jest.fn();
describe('<MyComponent />', () => {
const props = {};
mockDispatchFn.mockReturnValue(
[{data1},{data2}]
);
let componentRendering;
beforeEach(() => {
componentRendering = render(
<MyComponent props={ props } />
)});
it('test component', ()=> {
expect(componentRendering.container.innerHTML).not.toBeNull();
});
});
Throws me error:
Cannot read properties of undefined (reading 'then')

My assumption from the error message is that your code is expecting a promise somewhere.
The stack trace will have info about the component throwing this error at a specific line.
I would recommend you to check your code and mock all the required methods
I hope this helps in resolving the error

Have you tried to mock useDispatch instead mocking someActionMethod?
import * as redux from 'react-redux';
const useDispatchSpy = jest.spyOn(redux, 'useDispatch');
const mockDispatchFn = jest.fn();
// One of these should work
mockDispatchFn.mockReturnValue([{data1}, {data2}]);
useDispatchSpy.mockReturnValue(mockDispatchFn);
With your updated response I'd try this:
describe('<MyComponent />', () => {
it('test component', () => {
const props = {};
const useDispatchSpy = jest.spyOn(reactRedux, 'useDispatch');
const mockDispatchFn = jest.fn();
mockDispatchFn.mockReturnValue(
[{ data1 }, { data2 }],
);
render(<MyComponent {...props} />);
// your assertions here
});
});

Are you using it with middleware? I am not sure that dispatch returns a promise nor any value without using middleware.
By the way, mocking a react redux hook is not a recommended way and you could actually see it is not even possible as of version 8 of react-redux package.

Related

I want to write the test cases for this file

enter image description here
enter image description here
enter image description here
Here is my test code
import React from 'react';
import Notification from '../Notification';
import Enzyme, { shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import * as redux from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../../Core/Reducers/index';
import renderer from 'react-test-renderer';
const store = createStore(rootReducer, applyMiddleware(thunk));
Enzyme.configure({ adapter: new Adapter() });
describe('Should render initial layout Notification', () => {
it('renders correctly', () => {
const prop = true;
const wrapper = shallow(<Provider store={store}><Notification {...prop} />
</Provider>
it('renders correctly', () => {
const spy = jest.spyOn(redux, 'useSelector');
spy.mockReturnValue('drafts');
});
it('renders correctly', () => {
const setdraftState = jest.fn();
jest
.spyOn(React, 'useState')
.mockImplementation(draftState => [draftState, setdraftState]);
});
it('renders correctly', () => {
const setVenueState = jest.fn();
jest
.spyOn(React, 'useState')
.mockImplementation(venueState => [venueState, setVenueState]);
});
it('renders correctly', () => {
const setAuditState = jest.fn();
jest
.spyOn(React, 'useState')
.mockImplementation(auditState => [auditState, setAuditState]);
});
it('renders correctly', () => {
const setAdminState = jest.fn();
jest
.spyOn(React, 'useState')
.mockImplementation(adminState => [adminState, setAdminState]);
});
it('renders correctly', () => {
const setAdminStateOM = jest.fn();
jest
.spyOn(React, 'useState')
.mockImplementation(adminStateOM => [adminStateOM, setAdminStateOM]);
});
it('renders correctly', () => {
const setInternalVenueState = jest.fn();
jest
.spyOn(React, 'useState')
.mockImplementation(internalVenueState => [internalVenueState, setInternalVenueState
]);
});
it('renders correctly', () => {
const prop = true;
const wrapper = shallow(<Provider store={store}><Notification {...prop} />
</Provider>); expect(wrapper.children().length).toEqual(1);
});
it('renders correctly', () => {
const wrapper = shallow(<Provider store={store}><Notification /></Provider>);
const openNotificationWithIcon = jest.fn();
expect(wrapper.instance(openNotificationWithIcon));
});
it('Render Notification', () => {
const notify = renderer.create(<Provider store={store}><Notification /></Provider>);
let tree = notify.toJSON();
expect(tree).toMatchSnapshot();
});
});
I write some test cases but it's giving me 33.36 test coverage few things as I showed you in the image want to cover I am new in jest and react I would appreciate If you assist me how can I cover all the test coverage
You need to test your all condition so your code coverage will be increased. So based on that you need to manage your API's response or your state value based on that you can cover that lines.
Example :
draftStatus you need to update status based on your condition and write test cases according to that.
Add, Delete, Failure etc...
You can use react-testing-library and react-hooks-testing-library
for testing your react component and react hooks.
Basic Hooks
Imagine we have a simple hook that we want to test:
import { useState, useCallback } from 'react'
export default function useCounter() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount((x) => x + 1), [])
return { count, increment }
}
To test useCounter we need to render it using the renderHook function provided by react-hooks-testing-library:
import { renderHook } from '#testing-library/react-hooks'
import useCounter from './useCounter'
test('should use counter', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count).toBe(0)
expect(typeof result.current.increment).toBe('function')
})
Generally, enzyme and the "shallow" (which does not execute effects) testing mindset are not really a thing any more nowadays. (Enzyme itself has been pretty much dropped by airbnb and is maintained by a single person at this point, often lagging behind on React by months).
I would want to encourage you to look into react-testing-library instead, which will also execute your effects (the fact that they are not executed means you are not testing them - that is what coverage tells you in the end).
Also, it shifts the whole "testing mindset" a lot towards "how would a user interact with this?", so it might make more sense for you to read through their tutorials than just providing a code example here.

Test React function components promises behaviour with Jest,

I have a React function component.
I pass a function to the component as a prop, that returns a promise.
I use that function on an onClick event and once the promise is resolved, I change the state of the component.
Something like:
import React, { useState } from 'react';
function myComponent({ aPromiseReturningFunction }) {
const [myState, setState] = useState('12');
const clickHandler = () => {
aPromiseReturningFunction().then(() => { setState('123') })
};
return <div onClick={ clickHandler }>{myState}</div>
}
Inside my test:
const myFunc = jest.fn(() => Promise.resolve(true));
const componentWrapper = shallow(<myComponent aPromiseReturningFunction={ myFunc }/>);
componentWrapper.simulate('click');
expect(componentWrapper.text()).toEqual('123');
Obviously the above fails, but I have not found anything that would explain how to properly test the above. Of course If I change the state outside the promise, the test passes.
Any suggestions?
Since click is updating the state after a promise aka asynchronously, I would use act
import { act } from 'react-dom/test-utils'; // other testing libraries have similar methods that test async events
const myFunc = jest.fn(() => Promise.resolve(true));
it('updates text after onclick', () => {
const componentWrapper = shallow(<myComponent aPromiseReturningFunction={ myFunc }/>);
act(() => {
componentWrapper.simulate('click');
});
expect(componentWrapper.text()).toEqual('123');
});
Thanks to alextrastero, I managed to come to a solution eventually.
What is missing from alextrastero's answer is that we should enclose the act() inside async/await like:
import { act } from 'react-dom/test-utils'; // other testing libraries have similar methods that test async events
const myFunc = jest.fn(() => Promise.resolve(true));
it('updates text after onclick', async () => {
const componentWrapper = shallow(<myComponent aPromiseReturningFunction={ myFunc }/>);
await act(() => {
componentWrapper.simulate('click');
});
expect(componentWrapper.text()).toEqual('123');
});
And in order for that to work, I also needed to use the regenerator-runtime/runtime package.

How to correctly test React with act

I'm trying to test a component but it errors with
console.error
Warning: An update to Example inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at Example (/Users/thr15/Developmemt/Boost/basket-creator/frontend/src/Page/F1/Example.tsx:5:29)
at WrapperComponent (/Users/thr15/Developmemt/Boost/basket-creator/frontend/node_modules/enzyme-adapter-utils/src/createMountWrapper.jsx:49:26)
Here's a simplified version of my component
import {useState} from 'react';
function Example(): JSX.Element {
const [name, setName] = useState('');
const [, setLoading] = useState(false);
const [results, setResults] = useState<number[]>([]);
/**
* Search the baskets.
*/
const search = async () => {
// Let the UI know we're loading
setLoading(true);
// Get the baskets
try {
const baskets: number[] = await (await fetch('/test?name=' + name)).json();
// Give the UI the data
setLoading(false);
setResults(baskets);
} catch (e) {
console.error(e);
}
};
return <div className={"content"}>
<input value={name} onChange={(e) => setName(e.target.value)}/>
<button onClick={search}>Search</button>
{results.length}
</div>
}
export default Example;
and my test so far
import Enzyme, {mount} from 'enzyme';
import Adapter from '#wojtekmaj/enzyme-adapter-react-17';
import Example from "./Example";
Enzyme.configure({adapter: new Adapter()});
describe('Example', () => {
test('searching requests the correct URL', () => {
fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([{a: 1}, {b: 2}]),
})
);
let searchButton;
const wrapper = mount(<Example/>);
const input = wrapper.find('input').at(0);
searchButton = wrapper.find('button').at(0);
input.simulate('change', {target: {value: 'Driver Name'}});
searchButton.simulate('click');
expect(searchButton.text()).toBe('Search');
expect(fetch.mock.calls.length).toBe(1);
expect(fetch.mock.calls[0][0]).toBe('/test?name=Driver Name');
});
});
I've tried wrapping act around various parts of the test, and it either still errors, or the name isn't appended to the query string. Can anyone point me in the right direction?
UPDATE:
Working test below for anyone (and probably me!) in the future
describe('Example', () => {
test('searching requests the correct URL', async () => {
fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([{a: 1}, {b: 2}]),
})
);
let searchButton: ReactWrapper;
const wrapper = mount(<Example/>);
const input = wrapper.find('input').at(0);
searchButton = wrapper.find('button').at(0);
input.simulate('change', {target: {value: 'Driver Name'}});
await act(async () => {
searchButton.simulate('click');
});
expect(searchButton.text()).toBe('Search');
expect(fetch.mock.calls.length).toBe(1);
expect(fetch.mock.calls[0][0]).toBe('/test?name=Driver Name');
});
});
I'm guessing that it is the clicking of the search button that is generating the act warning.
From react#16.9.0, act was changed to return a promise, meaning that you can avoid these types of warnings when testing async handlers.
Wrapping your search click simulation in an async act, might resolve the warning - but you might have to add a little timeout (not sure how this works with enzyme)
await act(() => {
searchButton.simulate('click');
})
Here are some more resources on the topic that might help you along the way:
secrets of the act(...) api
React’s sync and async act

Warning: An update to App inside a test was not wrapped in act(...) in enzyme and hooks

I have written this component. it fetchs data using hooks and state. Once it is fetched the loading state is changed to false and show the sidebar.
I faced a problem with Jest and Enzyme, as it does throw a warning for Act in my unit test. once I add the act to my jest and enzyme the test is failed!
// #flow
import React, { useEffect, useState } from 'react';
import Sidebar from '../components/Sidebar';
import fetchData from '../apiWrappers/fetchData';
const App = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const getData = async () => {
try {
const newData = await fetchData();
setData(newData);
setLoading(false);
}
catch (e) {
setLoading(false);
}
};
getData();
// eslint-disable-next-line
}, []);
return (
<>
{!loading
? <Sidebar />
: <span>Loading List</span>}
</>
);
};
export default App;
And, I have added a test like this which works perfectly.
import React from 'react';
import { mount } from 'enzyme';
import fetchData from '../apiWrappers/fetchData';
import data from '../data/data.json';
import App from './App';
jest.mock('../apiWrappers/fetchData');
const getData = Promise.resolve(data);
fetchData.mockReturnValue(getData);
describe('<App/> Rendering using enzyme', () => {
beforeEach(() => {
fetchData.mockClear();
});
test('After loading', async () => {
const wrapper = mount(<App />);
expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
const d = await fetchData();
expect(d).toHaveLength(data.length);
wrapper.update();
expect(wrapper.find('span').exists()).toEqual(false);
expect(wrapper.html()).toMatchSnapshot();
});
});
So, I got a warning:
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
I did resolve the warning like this using { act } react-dom/test-utils.
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import fetchData from '../apiWrappers/fetchData';
import data from '../data/data.json';
import App from './App';
jest.mock('../apiWrappers/fetchData');
const getData = Promise.resolve(data);
fetchData.mockReturnValue(getData);
describe('<App/> Rendering using enzyme', () => {
beforeEach(() => {
fetchData.mockClear();
});
test('After loading', async () => {
await act(async () => {
const wrapper = mount(<App />);
expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
const d = await fetchData();
expect(d).toHaveLength(data.length);
wrapper.update();
expect(wrapper.find('span').exists()).toEqual(false);
expect(wrapper.html()).toMatchSnapshot();
});
});
});
But, then my test is failed.
<App/> Rendering using enzyme › After loading
expect(received).toEqual(expected) // deep equality
Expected: false
Received: true
35 |
36 | wrapper.update();
> 37 | expect(wrapper.find('span').exists()).toEqual(false);
Does anybody know why it fails? Thanks!
"react": "16.13.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
This issue is not new at all. You can read the full discussion here: https://github.com/enzymejs/enzyme/issues/2073.
To sum up, currently in order to fix act warning, you have to wait a bit before update your wrapper as following:
const waitForComponentToPaint = async (wrapper) => {
await act(async () => {
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
});
};
test('After loading', async () => {
const wrapper = mount(<App />);
expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
// before the state updated
await waitForComponentToPaint(wrapper);
// after the state updated
expect(wrapper.find('span').exists()).toEqual(false);
expect(wrapper.html()).toMatchSnapshot();
});
You should not wrap your whole test in act, just the part that will cause state of your component to update.
Something like the below should solve your problem.
test('After loading', async () => {
await act(async () => {
const wrapper = mount(<App />);
});
expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
const d = await fetchData();
expect(d).toHaveLength(data.length);
await act(async () => {
wrapper.update();
})
expect(wrapper.find('span').exists()).toEqual(false);
expect(wrapper.html()).toMatchSnapshot();
});

How to test a component which is using a custom TypeScript React Hook?

I'm currently writing a React component in Typescript which makes use of a axios-hooks hook called useAxios. An example of this hook in use is here:
export const App = () => {
const [{ data, loading, error }, refetch] = useAxios(
"https://api.myjson.com/bins/820fc"
);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
return (
<div>
<button onClick={e => refetch()}>refetch</button>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
};
const rootElement = document.getElementById("root");
render(<App />, rootElement);
I'm trying to figure out how to write a test where I can mock the useAxios hook. I've tried creating a mock of the underlying axios component but I cant get this working:
import React from "react"
import { render } from "#testing-library/react"
import { Test } from "../test"
import useAxios from "axios-hooks"
jest.mock("axios-hooks")
const mockedAxios = useAxios as jest.Mocked<typeof useAxios>
it("Displays loading", () => {
// How to mock the return values in the following line?
// mockedAxios.
const { getByText } = render(<Test />)
expect(getByText("Loading...")).toBeDefined()
})
I know that I shouldn't have to mock axios which is the underlying dependency, I should be able to mock useAxios, but I though I'd try anyhow.
I realise that this question has been mentioned many times on SO, but I can find a solution to this particular use case.
Any help greatly appreciated!
Mock the module and setup the expected result of useAxios per test e.g.
jest.mock('axios-hooks');
import useAxios from 'axios-hooks';
test('App displays loading when request is fetching', () => {
useAxios.mockReturnValue(Promise.resolve({ loading: true }));
// mount component
// Verify "Loading" message is rendered
});
I figured out how to do this myself. To test the custom hook I did the following:
import * as useAxios from "axios-hooks"
jest.mock("axios-hooks")
const mockedAxios = useAxios as jest.Mocked<typeof useAxios>
it("Displays loading message", async () => {
// Explicitly define what should be returned
mockedAxios.default.mockImplementation(() => [
{
data: [],
loading: true,
error: undefined
},
() => undefined
])
const { getByText } = render(<Test />)
expect(getByText("Loading...")).toBeDefined()
})
I had to jump through some additional hoops to get the compiler happy with the () => undefined parameter. I'm not a fan of the double as, but I'm not sure how to make it less verbose as I'm new to TS.
import * as useAxios from 'axios-hooks';
import { AxiosPromise } from 'axios';
import React from 'react';
import Test from 'components/Test';
import { render } from '#testing-library/react';
jest.mock('axios-hooks');
const mockedUseAxios = useAxios as jest.Mocked<typeof useAxios>;
it('renders a loading message', async () => {
mockedUseAxios.default.mockImplementation(() => [
{
data: [],
loading: true,
error: undefined,
},
(): AxiosPromise => (undefined as unknown) as AxiosPromise<unknown>,
]);
const { getByText } = render(<Test />);
expect(getByText('Loading...')).toBeDefined();
});

Resources