How do i can test component in redux - reactjs

I have a problem testing the component
import React from "react";
import { useSelector } from "react-redux";
import { PopupListWrapper, Song } from "./PopupListStyles";
const PopupList = () => {
const selectedAlbum = useSelector((state) => state.album);
return (
<PopupListWrapper data-testid="popupList-test">
{selectedAlbum.map((song) => {
const { trackName, trackTime } = song;
return (
<Song key={trackName}>
<p>{trackName}</p>
<p>{trackTime}</p>
</Song>
);
})}
</PopupListWrapper>
);
};
export default PopupList;
I want to check if it redner, but I still have a problem with its internal function
import { render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";
import * as reactRedux from "react-redux";
import PopupList from "./PopupList";
describe("<PopupAlbumInformation/>", () => {
const initialState = {
selectedAlbum: [],
};
const mockStore = configureStore();
let store;
const useSelectorMock = jest.spyOn(reactRedux, "useSelector");
beforeEach(() => {
useSelectorMock.mockClear();
});
it("whether the component PopupList is rendering", () => {
store = mockStore(initialState);
useSelectorMock.mockReturnValue({ initialState });
const { selectedAlbum } = initialState;
const { getByTestId } = render(
<Provider store={store}>
<PopupList>{selectedAlbum}</PopupList>
</Provider>
);
});
});
And here is error
TypeError: selectedAlbum.map is not a function
10 | return (
11 | <PopupListWrapper data-testid="popupList-test">
> 12 | {selectedAlbum.map((song) => {
| ^
13 | const { trackName, trackTime } = song;
14 | return (
15 | <Song key={trackName}>
yes i know i don't have an assertion yet but because of this problem i can't go any further.
Any help?

There is no need to mock nor spy on the selector. Just wrap the component in redux provider, pass correct initial state and you are absolutely gucci

Related

Mocked dispatch being fired but state not changing

I think i'm misunderstanding some concept about jest functions, I'm trying to test if after a click my isCartOpen is being set to true; the function is working, being called with the desired value.
The problem is that my state isn't changing at all. I tried to set a spy to dispatch but i really can't understand how spy works or if it's even necessary in this case
// cart-icons.test.tsx
import { render, screen, fireEvent } from 'utils/test'
import CartIcon from './cart-icon.component'
import store from 'app/store'
import { setIsCartOpen } from 'features/cart/cart.slice'
const mockDispatchFn = jest.fn()
jest.mock('hooks/redux', () => ({
...jest.requireActual('hooks/redux'),
useAppDispatch: () => mockDispatchFn,
}))
describe('[Component] CartIcon', () => {
beforeEach(() => render(<CartIcon />))
it('Dispatch open/close cart action when clicked', async () => {
const { isCartOpen } = store.getState().cart
const iconContainer = screen.getByText(/shopping-bag.svg/i)
.parentElement as HTMLElement
expect(isCartOpen).toBe(false)
fireEvent.click(iconContainer)
expect(mockDispatchFn).toHaveBeenCalledWith(setIsCartOpen(true))
// THIS SHOULD BE WORKING, BUT STATE ISN'T CHANGING!
expect(isCartOpen).toBe(true)
})
})
// cart-icon.component.tsx
import { useAppDispatch, useAppSelector } from 'hooks/redux'
import { selectIsCartOpen, selectCartCount } from 'features/cart/cart.selector'
import { setIsCartOpen } from 'features/cart/cart.slice'
import { ShoppingIcon, CartIconContainer, ItemCount } from './cart-icon.styles'
const CartIcon = () => {
const dispatch = useAppDispatch()
const isCartOpen = useAppSelector(selectIsCartOpen)
const cartCount = useAppSelector(selectCartCount)
const toggleIsCartOpen = () => dispatch(setIsCartOpen(!isCartOpen))
return (
<CartIconContainer onClick={toggleIsCartOpen}>
<ShoppingIcon />
<ItemCount>{cartCount}</ItemCount>
</CartIconContainer>
)
}
export default CartIcon
// utils/test.tsx
import React, { FC, ReactElement } from 'react'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
import { ApolloProvider } from '#apollo/client'
import { Elements } from '#stripe/react-stripe-js'
import { render, RenderOptions } from '#testing-library/react'
import store from 'app/store'
import { apolloClient, injectStore } from 'app/api'
import { stripePromise } from './stripe/stripe.utils'
injectStore(store)
const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Provider store={store}>
<ApolloProvider client={apolloClient}>
<BrowserRouter>
<Elements stripe={stripePromise}>{children}</Elements>
</BrowserRouter>
</ApolloProvider>
</Provider>
)
}
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
) => render(ui, { wrapper: AllTheProviders, ...options })
export * from '#testing-library/react'
export { customRender as render }

Testing react component that uses Context - change state of component under test

I want to test the following React component
import React, { useContext, useState } from "react";
import { IntlProvider } from "react-intl";
export const Context = React.createContext();
const defaultLocale = navigator.language;
const LocalizationWrapper = (props) => {
const [locale, setLocale] = useState(defaultLocale);
function changeLocale(newLocale) {
setLocale(newLocale);
}
return (
<Context.Provider value={{ locale, changeLocale }}>
<IntlProvider locale={locale}>{props.children}</IntlProvider>
</Context.Provider>
);
};
For this, I have written test
import React from 'react';
import { render, unmountComponentAtNode } from "react-dom";
import { FormattedDate } from "react-intl";
import { act } from "react-dom/test-utils";
import LocalizationWrapper from './localizationWrapper';
let container = null;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("date formatted with default locale", () => {
act(() => {
render(
<LocalizationWrapper >
<FormattedDate value={new Date("2021-04-23")} />
</LocalizationWrapper>, container);
});
expect(container.textContent).toBe("4/23/2021");
});
How to add a test case that changes the locale that is part of LocalizationWrapper state, by using context or by changing state directly? From code outside of test I use context to change locale like so:
const context = useContext(Context);
context.changeLocale(locale);
You can render the FormattedDate component inside a test component so that you can get the context value. Then you can invoke the context.changeLocale function.
E.g.
index.jsx:
import React, { useState } from 'react';
import { IntlProvider } from 'react-intl';
export const Context = React.createContext();
const defaultLocale = navigator.language;
export const LocalizationWrapper = (props) => {
const [locale, setLocale] = useState(defaultLocale);
function changeLocale(newLocale) {
setLocale(newLocale);
}
return (
<Context.Provider value={{ locale, changeLocale }}>
<IntlProvider locale={locale}>{props.children}</IntlProvider>
</Context.Provider>
);
};
index.test.jsx:
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { act } from 'react-dom/test-utils';
import { FormattedDate } from 'react-intl';
import { LocalizationWrapper } from '.';
import { Context } from './';
describe('67228107', () => {
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it('date formatted with default locale', () => {
let renderedContext;
class TestComponent extends React.PureComponent {
static contextType = Context;
render() {
renderedContext = this.context;
return <FormattedDate value={new Date('2021-04-23')} />;
}
}
act(() => {
render(
<LocalizationWrapper>
<TestComponent />
</LocalizationWrapper>,
container
);
});
expect(container.textContent).toBe('4/23/2021');
act(() => {
renderedContext.changeLocale('fr');
});
expect(renderedContext.locale).toBe('fr');
});
});
Test result:
PASS stackoverflow/67228107/index.test.tsx (10.33 s)
67228107
✓ date formatted with default locale (36 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.jsx | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.615 s
package versions:
"react": "^16.14.0",
"react-dom": "^16.14.0",
"jest": "^26.6.3"

Why is my reducer not triggered using redux?

Good morning everyone,
I just set up redux on my react project. What i am willing to do is to get data when the component Dashboard is rendered. Data are expenses and incomes variables, which are the sum of all the expenses and incomes.
The problem i get is that my reducer is never triggered. I get no error in my console.
As you can see in actions/dashboard.js, i consoled log when the call to fetchdata is done, and is working correctly. but the call to setBalance does not.
In the reducers/dashboard.js, in the switch case, the default case is always triggered, it's like it does not find the action type.
My Component Dashboard.js
import React, {useEffect} from 'react'
import classes from './Dashboad.module.css'
import { Doughnut } from 'react-chartjs-2'
import {connect} from 'react-redux'
import * as actions from '../../store/actions/index'
const DashBoard = (props) => {
useEffect(() => {
props.onFetchData()
}, [])
// ----------------------------------- Hidden code to not overload the topic -------------------
const mapStateToProps = state => {
return {
incomes: state.incomes,
expenses: state.expenses
}
}
const mapDispatchToProps = dispatch => {
return {
onFetchData: () => dispatch(actions.fetchData),
}
}
export default connect(mapStateToProps,mapDispatchToProps)(DashBoard);
actions/index.js :
export {
fetchData
} from './dashboard'
actions/actionsTypes.js :
export const SET_BALANCE = 'SET_BALANCE'
actions/Dashboard.js
import * as actionsTypes from './actionsTypes'
import axios from '../../axios'
export const setBalance = (donnees) => {
console.log('setbalance')
return {
type: actionsTypes.SET_BALANCE,
donnees: donnees
}
}
export const fetchData = () => {
console.log('call fetchdata')
return dispatch => {
axios.get('/movements.json')
.then(response => {
dispatch(setBalance(response.data))
console.log(response.data)
})
.catch(error => console.log(error))
}
}
Reducers/Dashboard.js :
import * as actionsTypes from '../actions/actionsTypes'
const initialState = {
incomes: 0,
expenses: 0
}
const setBalance = (state, action) => {
let fetchedMvts = []
for(let key in action.donnees.data) {
fetchedMvts.push({...action.donnees.data[key]})
}
let sumIncome = 0;
let sumOutcome = 0;
fetchedMvts.forEach(element => {
let parsed = parseInt(element.amount, 10)
if(element.type === "income") {
sumIncome = sumIncome + parsed;
} else {
sumOutcome = sumOutcome + parsed;
}
})
return {...state, incomes: sumIncome, expenses: sumOutcome}
}
const reducer = (state= initialState, action) => {
console.log('TYPE', action.type)
switch (action.type) {
case actionsTypes.SET_BALANCE: return setBalance(state, action);
default:
console.log('problem')
return state;
}
}
export default reducer;
And, in case you need it, the index.js file of my app :
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux'
import { createStore, applyMiddleware} from 'redux'
import * as serviceWorker from './serviceWorker';
import RootReducer from './store/reducers/dashboard'
import thunk from 'redux-thunk'
const store = createStore(RootReducer, applyMiddleware(thunk))
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
Thank you for your help guys
Have a nice day

Error with testing custom react hooks using jest and enzymne

When using custom hooks with jest and enzymne I get this error 'TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined'
Tried various things but nothing seems to work. Please help
import React, { useEffect, useState, Fragment } from 'react';
/* Utils */
import { useSystemContext } from '../../../utils/Context/SystemContextProvider';
/* Components */
import MenuStructure from './MenuStructure';
const Menu = () => {
const [{ siteData }] = useSystemContext();
const [open, setOpen] = React.useState(false);
const openMenu = () => {
setOpen(!open);
};
const componentProps = {
menuItems: siteData,
openMenu: openMenu,
open: open
};
return (
<MenuStructure {...componentProps} />
)
};
export default Menu;
Test
import { shallow, mount } from 'enzyme';
import Menu from '.';
import { testUseState } from '../../../utils/TestUtils';
describe('<Menu />', () => {
let wrapper;
it('calls setCount with 0', () => {
wrapper = mount(<Menu />);
});
});
Error
TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
9 | import MenuStructure from './MenuStructure';
10 |
> 11 | const Menu = () => {
| ^
12 | const [{ siteData }] = useSystemContext();
13
added useSystemContext as requested
import React, { createContext, useContext, useReducer } from 'react';
export const SystemContext = createContext();
export const SystemContextProvider = ({ reducer, initialState, children }) => (
<SystemContext.Provider value={useReducer(reducer, initialState)}>
{children}
</SystemContext.Provider>
);
export const useSystemContext = () => useContext(SystemContext);
This is works fine in the site when running

console.log(Wrapper.debug()) showing <ContextConsumer> [function] </ContextConsumer>

I am using jest and enzyme for the unit testing in the react js.
When I console log the wrapper returned by shallow function by using "console.log(wrapper.debug())" it returns [function] and I am not able to find attribute in the component.
This is my signUp component
signUp.js
import React, { Component } from 'react'
import { withStyles, Card, CardContent, Typography, MenuItem } from '#material-ui/core';
import { darken } from '#material-ui/core/styles/colorManipulator';
import { FuseAnimate } from '#fuse';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux';
import { Link, withRouter } from 'react-router-dom';
import classNames from 'classnames';
import * as Actions from '../../../auth/store/actions/register.actions';
import * as FuseActions from '../../../store/actions/fuse/message.actions';
import Formsy from 'formsy-react';
import { TextFieldFormsy } from '#fuse';
import { Button, InputAdornment, Icon, Grid } from '#material-ui/core';
const styles = theme => ({
root: {
background: 'linear-gradient(to right, ' + theme.palette.primary.dark + ' 0%, ' + darken(theme.palette.primary.dark, 0.5) + ' 100%)',
color: theme.palette.primary.contrastText
}
});
class SignUp extends Component {
render() {
const { classes } = this.props;
const { canSubmit } = this.state;
return (
<div data-test="data-component" className={classNames(classes.root, "flex flex-col flex-1 flex-no-shrink p-24 md:flex-row md:p-0")}>
.........
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
userSignup: Actions.userSignup,
disableErrors: Actions.disableErrors,
showMessage: FuseActions.showMessage
}, dispatch);
}
function mapStateToProps({ auth }) {
return {
register: auth.register
}
}
export default withStyles(styles, { withTheme: true })(withRouter(connect(mapStateToProps, mapDispatchToProps)(SignUp)));
This is test file
signUp.test.js
import React from 'react';
import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import SignUp from './signUp';
import AppContext from '../../../AppContext';
configure({ adapter: new Adapter() });
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const setup = (initialState = {}) => {
const store = mockStore(initialState);
const wrapper = shallow(<SignUp store={store} />).dive().dive();
console.log(wrapper.debug());
return wrapper;
}
const findAttrByTest = (wrapper, val) => {
return wrapper.find(`[data-test="${val}"]`);
}
describe("render <SignUp>", () => {
let wrapper;
beforeEach(() => {
const initialState = {
error: '',
success: false
};
wrapper = setup(initialState);
});
test("render component without error", () => {
const component = findAttrByTest(wrapper, 'data-component');
expect(component.length).toBe(1);
});
});
Test result is
FAIL signUp.test.js (5.199s)
render <SignUp>
✕ render component without error (45ms)
● render <SignUp> › render component without error
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
43 | test("render component without error", () => {
44 | const component = findAttrByTest(wrapper, 'data-component');
> 45 | expect(component.length).toBe(1);
| ^
46 | });
47 | });
48 |
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 6.722s
Ran all test suites related to changed files.
Watch Usage: Press w to show more. console.log signUp.test.js:24
<ContextConsumer>
[function]
</ContextConsumer>
I have solved this issue by using.WrappedComponent.By this, you get access to the component
import React from 'react';
import { configure, shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import SignUp from './signUp';
configure({ adapter: new Adapter() });
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const push = jest.fn();
const historyMock = { push: jest.fn() };
const setup = (initialState = {}, props = {}) => {
const store = mockStore(initialState);
const wrapper = shallow(<SignUp.WrappedComponent history={historyMock} {...props} store={store} params={{
router:
push
}} />);
return wrapper;
}
const findAttrByTest = (wrapper, val) => {
return wrapper.find(`[data-test="${val}"]`);
}
describe("render <SignUp>", () => {
let wrapper;
beforeEach(() => {
const initialState = {
error: '',
success: false
};
wrapper = setup(initialState);
});
test("render component without error", () => {
const component = findAttrByTest(wrapper, 'data-component');
expect(component.length).toBe(1);
});
});

Resources