useSelector of Undefined - reactjs

Hi I am writting unit testing for a component it has redux in it. While testing it , throws an error UseSelector state undefined. UseSelector will be updated once after getting response from the api. Till that its value is undefined. But while unit tetsing it throws error on the first itself . How to overcome this issue.
Test File
import React from 'react';
import { render, fireEvent } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import '#testing-library/jest-dom/extend-expect';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import LinkApplication from '../index';
import {reducer} from '../../../redux/summary/reducer';
let INITIAL_STATES = {
isFetching: false,
errorMessage: "",
requestExtensionErrorMessage: "",
requestExtensionSuccessMessage: "",
linkApplicationSuccess: "",
linkApplicationFailure: null,
linkLoanSuccessMessage: "",
linkLoanErrorMessage: "",
LinkApplicationErrorMessage:"",
};
function renderWithRedux(
component,
{ INITIAL_STATES, store = createStore(reducer, INITIAL_STATES) } = {},
) {
return {
...render(<Provider store={store}>{component}</Provider>),
};
}
it('sumbits the form', async () => {
const onSubmit = jest.fn();
const { getByText, getByTestId } = renderWithRedux(
<LinkApplication onSubmit={onSubmit} />,
);
const Dob_Input = getByTestId('dob-input');
const Phone_Input = getByTestId('phone-input');
const form = getByTestId('form');
act(() => {
fireEvent.keyPress(Dob_Input, {
target: { value: '1995-09-27' },
});
fireEvent.keyPress(Phone_Input, { target: { value: '9500902621' } });
});
expect(Dob_Input.value).toBe('1995-09-27');
expect(Phone_Input.value).toBe('9500902621');
await act(() => {
fireEvent.submit(form);
});
expect(onSubmit).not.toHaveBeenCalled();
})
Component
const LinkApplication = () => {
const dispatch = useDispatch();
let LinkApplicationErrorMessage = useSelector(
state => state.summary.linkApplicationFailure
);
}
Error
TypeError: Cannot read property 'linkApplicationFailure' of undefined
const dispatch = useDispatch();
let LinkApplicationErrorMessage = useSelector(
state => state.summary.linkApplicationFailure
^
);
Please help me with that.

I think you need to mock react-redux
import * as React from 'react';
import { shallow } from 'enzyme';
jest.mock('react-redux', () => ({
useDispatch: () => {},
useSelector: () => ({
your: 'state',
}),
}));
import Component from './index';
describe('Test component', () => {
it('Should render and match the snapshot', () => {
const wrapper = shallow(<Component />);
});
});

Related

React component test case coverage of inline functions

Can someone help me cover this test case, I am not able to figure out how to cover this inline function
Note: DropdownField is a wrapper component and contains the actual which is imported from
import { Field } from "redux-form";
dropdown input inside
I have tried to call mockfunction and jest.fn() but nothing works, Any help will be appreciated because I am totally blank at the moment. Thanks in advance to all the wonderful devs
import React from "react";
import DropdownField from "components/FormFields/DropdownField";
import get from "lodash/get";
 
const AddressLookup = props => {
const {
change,
formValues,
fetchAddressLookup,
postalCodeOptions,
type = "delivery",
placeholder = "type_to_search",
field
} = props;
const selectedDeliveryMethod = get(formValues, "delivery_method", {});
 
return (
<DropdownField
placeholder={placeholder}
options={postalCodeOptions}
{...selectedDeliveryMethod.fields.zip_code}
isSearchable={true}
field={field}
onInputChange={value => {
if (value.length >= 2) fetchAddressLookup({ q: value });
}}
onChange={({ value }) => {
const [city, state, zipCode] = value.split("-");
change(field, value);
change(`${type}_state`, state);
change(`${type}_city`, city);
change(`${type}_zip_code`, zipCode);
}}
/>
);
};
 
export default AddressLookup;
I have tried this approach but It failed to cover. First test case covers the UI part only as you can see it is matching to snapshot. In second test cases I removed some code and commented some because nothing works
import * as React from 'react';
import { render, fireEvent, wait } from '#testing-library/react';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import { reduxForm } from 'redux-form';
import configureStore from 'redux-mock-store';
import messages from '__fixtures__/messages.json';
import AddressLookup from '../index';
const DecoratedAddressLookup = reduxForm({ form: 'testForm' })(AddressLookup);
const testProps = {
change: jest.fn(),
fetchAddressLookup: jest.fn(),
postalCodeOptions: [
{
name: 'abc-abcd-1234',
value: 'abc-abcd-1234',
},
],
formValues: {
delivery_method: {
fields: {
zip_code: 'BD-BDBD-1234',
},
},
},
field: 'zip_code',
};
describe('<AddressLookup />', () => {
let testStore;
let addressField;
const mockStore = configureStore([]);
const store = mockStore({});
const spy = jest.fn();
beforeAll(() => {
testStore = store;
});
const renderComponent = () => {
return render(
<Provider store={testStore}>
<IntlProvider locale='en' messages={messages}>
<DecoratedAddressLookup
{...testProps}
onInputChange={spy}
onChange={spy}
/>
</IntlProvider>
</Provider>
);
};
it('should render and match the snapshot', () => {
const {
getByTestId,
container: { firstChild },
} = renderComponent();
addressField = getByTestId('zip_code');
expect(firstChild).toMatchSnapshot();
});
it('should type a value', async () => {
addressField = addressField.querySelector('input');
// expect(addressField).toBeTruthy();
// console.log('addressField', addressField);
// const input = screen.getByTestId('add-word-input');
fireEvent.change(addressField, { target: { value: 'abc-abcd-1234' } });
expect(addressField).toHaveValue('abc-abcd-1234');
// expect(testProps.change).toBeCalled();
await wait(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});

Snapshot Testing With Jest, React and Redux

I'm trying to create a snapshot of my component which is using some custom useSelector and useDispatch hooks my boss created.
import { createDispatchHook, createSelectorHook } from "react-redux";
const Context = React.createContext(null);
export default Context;
export const useDispatch = createDispatchHook(Context);
export const useSelector = createSelectorHook(Context);
In my component the useSelector & useDispatch hooks are being called so I used jest.mock() on the hooks but then I get thrown an error saying TypeError: (0 , _reactRedux.createDispatchHook) is not a function. I can't find any documentation on how to mock a custom hook or how to even fix this issue.
import React, { createContext } from 'react';
import renderer from 'react-test-renderer';
import DecisionSidebar from './DecisionSidebar';
import { cleanup } from '#testing-library/react';
jest.mock('react-redux', () => ({
useDispatch: () => { },
useSelector: () => ({
project: {
myId: 0,
isProjectAdmin: true,
}
}),
}));
afterEach(cleanup);
describe('DecisionSidebar Snapshot Test', () => {
it('renders correctly with data', () => {
const component = renderer.create(<DecisionSidebar />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
I have also tried this in my jest file which produces a different error (which I have tried to fix since this one is easier and there are a lot of fixes, but still no luck)
const mockContext = React.createContext(null);
const mockCreateDispatchHook = () => new createDispatchHook()
const mockCreateSelectorHook = () => new createSelectorHook();
jest.mock('react-redux', () => ({
...jest.requireActual("react-redux"),
useSelector: () => mockCreateSelectorHook(mockContext),
useDispatch: () => mockCreateDispatchHook(mockContext),
}));
Using the the way from the redux website as suggested
import React from "react";
import { render } from "#testing-library/react";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import rootReducer from 'reducers';
const Context = React.createContext(null);
const renderer = (
ui,
{
initialState,
store = createStore(rootReducer, initialState, applyMiddleware(thunk)),
...renderOptions
} = {}
) => {
const Wrapper = ({ children }) => {
return (
<Provider
store={store}
context={Context}
>
{children}
</Provider>
);
}
return render(ui, { wrapper: Wrapper, ...renderOptions });
};
export * from "#testing-library/react";
export { renderer }
describe('DecisionSidebar Snapshot Test', () => {
it('renders correctly with data', () => {
const component = renderer(<DecisionSidebar />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
Still produces the same error

Testing a React with React-Router v.5, useHistory, useSelector and useEffect

I struggle with writing a proper test for a component that protects some routes and programatically redirects unauthorized users. The component looks like this:
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { isAuthed } from 'redux/selectors/auth';
const mapState = state => ({
isAuthenticated: isAuthed(state),
});
const LoginShield = ({ children }) => {
const { isAuthenticated } = useSelector(mapState);
const history = useHistory();
useEffect(() => {
if (!isAuthenticated) {
history.push('/login');
}
}, [isAuthenticated]);
return children;
};
export default LoginShield;
I basically would like to check that the component redirects unauthenticated user and doesn't redirect an authenticated user (two basic test cases). I tried several approaches using Jest/Enzyme or Jest/ReactTestingLibrary and cannot find a good solution.
For now my test is a mess but I will share it so that someone can show me where the problem lays:
import React, { useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import rootReducer from 'redux/reducers';
import LoginShield from 'components/LoginShield/LoginShield';
describe('LoginShield component', () => {
let wrapper;
let historyMock;
beforeEach(() => {
const initialState = { auth: { loginId: 'Foo' } };
const store = createStore(rootReducer, initialState);
historyMock = {
push: jest.fn(),
location: {},
listen: jest.fn(),
};
jest.mock('react-redux', () => ({
useSelector: jest.fn(fn => fn()),
}));
wrapper = mount(
<Provider store={store}>
<Router history={historyMock}>
<LoginShield>
<h5>Hello Component</h5>
</LoginShield>
</Router>
</Provider>,
);
});
it('renders its children', () => {
expect(wrapper.find('h5').text()).toEqual('Hello Component');
});
it('redirects to the login page if user is not authenticated', async () => {
await act(async () => {
await Promise.resolve(wrapper);
await new Promise(resolve => setImmediate(resolve));
wrapper.update();
});
// is the above necessary?
console.log(historyMock.push.mock.calls);
// returns empty array
// ... ?
});
it('doesn`t redirect authenticated users', () => {
// .... ?
});
});
Any tips are more than welcome! Thank you in advance. :)

I'm using a useDispatch custom hook and getting this error in my test: Actions must be plain objects. Use custom middleware for async actions

I'm trying to test that a function gets called with the correct arguments, but because I'm using useDispatch from react-redux, I'm getting the following error: Actions must be plain objects. Use custom middleware for async actions.
I've wrapped my test component in renderWithRedux like it says in the documentation.
Test Setup:
const renderWithRedux = (
ui,
{ initialState, store = createStore(reducer, initialState) } = {}
) => {
return {
...render(<Provider store={store}>{ui}</Provider>),
store,
};
};
const templateId = faker.random.number();
const setup = () => {
const props = {
history: {
push: jest.fn(),
},
};
viewTemplateUrl.mockImplementationOnce(() => jest.fn(() => () => {}));
templatePostThunk.mockImplementationOnce(
jest.fn(() => () => Promise.resolve(templateId))
);
const {
container,
getByText,
getByLabelText,
rerender,
debug,
} = renderWithRedux(<NewTemplateForm {...props} />);
return {
debug,
templateId,
props,
container,
rerender,
getByText,
getByLabelText,
templateNameTextField: getByLabelText('Template Name'),
templateNameInput: getByLabelText('Template Name Input'),
saveTemplateButton: getByText('Save Template'),
cancelButton: getByText('Cancel'),
};
};
Failing test:
test('save template calls handleSubmit, push, and viewTemplateUrl', async () => {
const { templateNameInput, saveTemplateButton, props } = setup();
fireEvent.change(templateNameInput, { target: { value: 'Good Day' } });
fireEvent.click(saveTemplateButton);
await expect(templatePostThunk).toHaveBeenCalledWith({
name: 'Good Day',
});
expect(props.history.push).toHaveBeenCalled();
expect(viewTemplateUrl).toHaveBeenCalledWith({ templateId });
});
It should be passing.
The answer was to mock useDispatch and wrap the parent component in renderWithRedux instead.
import { render, fireEvent } from '#testing-library/react';
import '#testing-library/react/cleanup-after-each';
import { useDispatch } from 'react-redux';
import faker from 'faker';
import { NewTemplateForm } from './NewTemplateForm';
import { templatePostThunk } from '../../../redux/actions/templatePost';
import { viewTemplateUrl } from '../../../utils/urls';
jest.mock('../../../redux/actions/templatePost');
jest.mock('../../../utils/urls');
jest.mock('react-redux');
describe('<NewTemplateForm/> controller component', () => {
useDispatch.mockImplementation(() => cb => cb());
const setup = () => {
const props = {
history: {
push: jest.fn(),
},
};
const templateId = faker.random.number();
viewTemplateUrl.mockImplementationOnce(() => () => {});
templatePostThunk.mockImplementationOnce(() => async () => ({
data: { id: templateId },
}));
const { container, getByText, getByLabelText, rerender, debug } = render(
<NewTemplateForm {...props} />
);
You're trying to dispatch a thunk action creator:
await expect(templatePostThunk).toHaveBeenCalledWith({
name: 'Good Day',
});
But you're setting up the store without the thunk middleware:
store = createStore(reducer, initialState)
You would need to add the thunk middleware to the createStore() call for this to work right.

React-Redux component test fails when using async actions

I'm trying to test react-redux connected app which is having aync action to fetch data from API. But the test fails for some reason. what wrong i'm doing here?
AssertionError: expected { length: 0 } to have a length of 1 but got 0
posts.js(Redux Action)
import instance from "./../config/axiosconfig";
export const postList = () => {
return dispatch => {
return instance.get("/posts")
.then(res => {
const posts = res.data;
return dispatch({
type: "POST_LIST", posts
});
});
};
};
posts.js(React Component)
import React, { Component } from "react";
import { connect } from "react-redux";
import {postList} from "../actions/posts";
import PropTypes from "prop-types";
class Post extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
if (this.props.posts === undefined || this.props.posts.length === 0) {
const {dispatch} = this.props;
dispatch(postList());
}
}
render() {
let postLists = "";
if (this.props.posts) {
postLists = this.props.posts.map((list, i) => (
<li key = {i}>
{list.title}
</li>
));
}
return (
<div>
<h2>About</h2>
<p>Services</p>
<ol className="post-list">
{postLists}
</ol>
</div>
);
}
}
Post.propTypes = {
posts: PropTypes.array,
dispatch: PropTypes.func
};
const mapDispatchToProps = (dispatch) => ({ dispatch });
const mapStateToProps = (state) => {
return { posts: state.PostReducer ? state.PostReducer.posts : [] };
};
export default connect(mapStateToProps, mapDispatchToProps)(Post);
Post.test.js(Test for component)
import React from "react";
import Post from "client/components/post";
import { Provider } from 'react-redux';
import PostReducer from "client/reducers/posts";
import {mount, render, shallow} from 'enzyme'
import instance from "client/config/axiosconfig";
import { expect } from "chai";
import moxios from "moxios";
import { createStore, applyMiddleware, combineReducers } from 'redux';
import configureStore from "redux-mock-store";
import thunkMiddleware from "redux-thunk";
let store;
let wrapper;
describe("Post Component", () => {
beforeEach(() => {
moxios.install(instance);
});
afterEach(() => {
moxios.uninstall(instance);
});
it("has expected posts lists listed", async () => {
store = setupStore();
const payload = [{
body: "TEST",
id: 1,
title: "Test Title"
}];
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: payload
});
});
wrapper = mount(<Provider store={store}><Post/></Provider>);
expect(wrapper.find('.post-list li')).to.have.length(1);
});
function setupStore() {
return createStore(
combineReducers({ PostReducer }),
applyMiddleware(thunkMiddleware)
);
}
});
The request which you have stubbed may still be pending by the time make an assertion.
Try the following:
it("has expected posts lists listed", async () => {
store = setupStore();
const payload = [{
body: "TEST",
id: 1,
title: "Test Title"
}];
const promise = moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: payload
});
});
const wrapper = mount(<Provider store={store}><Post/></Provider>);
await promise;
wrapper.update(); // May not be necessary
expect(wrapper.find('.post-list li')).to.have.length(1);
});

Resources