React Testing Library - Cannot read property 'contents' of undefined] - value from redux - javascript

Am new in writing testcases using React Test library.
Here is my component
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
class MyContainer extends React.Component {
static propTypes = {
graphicalData: PropTypes.object.isRequired,
};
render() {
const { graphicalData } = this.props;
return (
graphicalData && (
<div>
/////some action and rendering
</div>
))}
}
const mapStateToProps = (state) => {
return {
graphicalData: state.design.contents ? state.design.contents.graphicalData : {},
};
};
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(MyContainer)));
So i am writing my test case file using React Testing library
import React from 'react';
import '#testing-library/jest-dom';
import { render, cleanup, shallow } from '#testing-library/react';
import MyContainer from '../../MyContainer';
import configureMockStore from 'redux-mock-store';
import ReactDOM from 'react-dom';
const mockStore = configureMockStore();
import { Provider } from 'react-redux';
const store = mockStore({
state: {
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
},
});
afterEach(cleanup);
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<MyContainer />
</Provider>,
div
);
ReactDOM.unmountComponentAtNode(div);
});
Am not sure what went wrong , my idea is to check if the component loads without crashing , also if the cars array length is greater than 0 , check something rendered on page.
But am getting some error, any help with example or suggestion will save my day

The error seems correct. Check the structure that you have passed into the mockStore() function.
It is
state: {
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
},
So, when you access in your MyContainer, you should access it as
state.state.design.contents
It is just that you have an extra hierarchy in the state object.
Or, if you don't want to change the component, change the structure of the state passed into your mockStore() method as:
const store = mockStore({
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
});

Related

Cannot shallow render a component using IntlProvider

I have the following component, using Flow:
//#flow
import React, { type Node } from 'react';
import { useIntl } from 'react-intl';
type Props = { balance: Object };
const AvailableDiscount = ({ balance }: Props): Node => {
const { formatMessage, locale } = useIntl();
return (
<div>
{formatMessage({ id: 'XAM_DISCOUNT_DETAILS' })}: {balance.value}
</>
);
};
And while testing it, I seem to have a problem when trying it so mount it with shallow, using Enzyme:
// #flow
import { mount, shallow } from 'enzyme';
import React from 'react';
import { IntlProvider } from 'react-intl';
import balance from '../../../utils/testHelpers/testData/customerBalance';
import AvailableDiscount from './AvailableDiscount';
describe('AvailableDiscount', () => {
it('renders correctly', () => {
const component = <AvailableDiscount balance={balance} />;
const wrappingOptions = {
wrappingComponent: IntlProvider,
wrappingComponentProps: {
locale: 'en',
defaultLocale: 'en',
messages: {},
},
};
const mountedComponent = mount(component, wrappingOptions); // <-- This works
const shallowComponent = shallow(component, wrappingOptions); // <-- This does NOT work
});
});
It tells me that the component does not seem to be wrapped in the provider.
While this seems to work for mount, shallow keeps giving me this error. Why could this be?

Unable to render React function using ReactDOM

I'm trying to write tests for the rendering function Game(), which is for a Connect 4 react page.
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import * as useGame from "./hooks/use-game";
import Game from ".";
const STATE_SPY = jest.spyOn(useGame, "default");
STATE_SPY.mockReturnValue({
winner: "",
dimensions: {
numRows: 4,
numCols: 4,
},
squares: [
["", "", "", ""],
["", "", "", ""],
["", "", "", ""],
["", "", "", ""],
],
});
const { container } = render(<Game />, document.getElementById("game"));
When I run the tests with npm test, I get Target container is not a DOM element.
If I create an element around the error I get a different error:
const { container } = render(<Game />, document.createElement("div"));
TypeError: Cannot destructure property 'container' of '(0 , _reactDom.render)(...)' as it is null.
Any idea how I need to write this render() function properly? I've searched all over the Internet, but it seems that I have set this up in a pretty standard way.
Here's the code being tested:
import React from "react";
import { useGame } from "./hooks";
import { Board, Settings } from "./components";
import { DARK_SYMBOL, LIGHT_SYMBOL, UseStyles } from "styles/styles";
import { dimensionsFormInput } from "types/form-inputs";
const INIT_ROW = 6;
const INIT_COL = 7;
// Game keeps track of the active player and winners of the Connect Four game
export default function Game(): JSX.Element {
const {
dimensions,
setDimensions,
squares,
darkIsNext,
winner,
handleSquareClick,
} = useGame(INIT_ROW, INIT_COL);
const classes = UseStyles();
return (
<div id="game">
<Board
squares={squares}
onClick={(row: number, col: number) => handleSquareClick(row, col)}
winner={winner}
/>
<span className={classes.blueText}>
{winner.length > 0
? "Winner is " + winner
: "Next piece: ".concat(darkIsNext ? DARK_SYMBOL : LIGHT_SYMBOL)}
</span>
<Settings
dimensions={dimensions}
onSubmit={(data: dimensionsFormInput) => setDimensions(data)}
/>
</div>
);
}
did you try to create your own render function?, something like this, adapting the function to your needs, and use it in your test:
import React from 'react';
import { render as rtlRender } from '#testing-library/react';
import ConnectedRouter from 'react-router-redux/ConnectedRouter';
import { Provider } from 'react-redux';
import ReduxConnectedIntlProvider from '../ReduxConnectedIntlProvider';
import ThemeContext from '../context/theme-context';
import { store } from '../store';
import theme from '../theme/theme';
import { createMemoryHistory } from 'history';
const render = (
ui,
{
route = '/',
history = createMemoryHistory({ initialEntries: [route] }),
...renderOptions
} = {}
) => {
const Wrapper = ({ children }) => {
return (
<Provider store={store}>
<ReduxConnectedIntlProvider>
<ThemeContext.Provider value={theme}>
<ConnectedRouter history={history}>{children}</ConnectedRouter>
</ThemeContext.Provider>
</ReduxConnectedIntlProvider>
</Provider>
);
};
return { ...rtlRender(ui, { wrapper: Wrapper, ...renderOptions }), history };
};
// re-export everything
export * from '#testing-library/react';
// override render method
export { render };
The code for the ReduxConnectedIntlProvider:
import { connect } from 'react-redux';
import { IntlProvider } from 'react-intl';
function mapStateToProps(state) {
const { language, messages } = state.intl;
return { locale: language, key: language, messages };
}
export default connect(mapStateToProps)(IntlProvider);
Regards.
This import is correct? import Game from "." i think that the correct is: import Game from "./"
Instead of using render from ReactDOM, I used render from 1#testing-library/react`. This allows me to conduct the tests I want to.

React-Redux connect isn't getting store from Provider

Im using Redux with React Native to manage state. I believe that I've successfully set up the store and Provider. I can use store.getState() and store.dispatch(action()) from any component successfully, however, the react-redux connect function is not allowing me to access the store from child components. Can you find anything wrong with my code below?
Login.js - This child component I'm testing won't access redux store with react-redux connect.
import React, {Component} from 'react';
import actions from '../../redux/actions';
import {connect} from 'react-redux';
const mapStateToProps = state => {
// To test if this function fires, which it is not
console.log('login state mapping through redux');
return {
state: state,
};
};
const dispatchToProps = dispatch => {
return {
userRecieved: (user) => dispatch(actions.userRecieved(user)),
};
};
export class Login extends Component {
constructor(){
super();
this.state = {
credentials: {
email: '',
password: '',
},
};
}
componentDidMount(){
// This will show whether redux is connected
console.log(this.props.state);
this.props.userRecieved('TEST USER');
}
render() {
return ( <Text>{this.props.state}</Text> );
}
}
export default connect(mapStateToProps, dispatchToProps)(Login);
App.js
import React, {Component} from 'react';
import YEET from './src/YEET.js';
import store from './src/redux/stores/index';
import {Provider} from 'react-redux';
export default class App extends Component {
render() {
return (
<Provider store={store}>
<YEET />
</Provider>
);
}
}
My Redux Files:
store.js
import { combineReducers, createStore} from 'redux';
import accountReducer from '../reducers/accountReducer';
import postReducer from '../reducers/postReducer';
const initialState = {};
const reducers = combineReducers({
account: accountReducer,
post: postReducer,
});
const store = createStore(reducers, initialState);
export default store;
actions.js
import constants from '../constants';
var userRecieved = user => ({
type: constants.USER_RECIEVED,
data: user,
});
export default {
userRecieved,
};
accountReducer.js
import constants from '../constants';
var initialState = {
user: {
photos: [],
},
};
export default (state = initialState, action ) => {
let newState = Object.assign({}, state);
switch (action.type) {
case constants.USER_RECIEVED:
const user = {
id: action.data.uid,
// photos: action.data,
};
console.log(action);
newState.user = user;
return newState;
default:
return state;
}
};
From what I see, the only reason could be that you're importing the unconnected component.
When you import the Login component, make sure that you import the default export instead of the named export.
So, wherever you import the Login component, do it like this:
import Login from 'your-login-component-location/Login'
instead of
import { Login } from 'your-login-component-location/Login'
The second one is a named export, which will return the Login class directly.
The first one is the default export, which will return the connected component.

Enzyme mount test failing with redux store state update

I have a simple component with a button, that when pressed fetches a comments JSON from placeholder API.
My enzyme test with mount() is failing, even though I can see that the state is updating in the CommentList component.
My manual tests in the browser display the comments fine.
My test with mount and a mock store passes.
I can even see that 2 li elements are created if I debug or console.log in CommentList.
Does it seem like the view is not being updated in mount after the redux state change?
Apologies for the amount of code below, I'm not sure which part is the culprit. The project can be cloned from https://github.com/Hyllesen/react-tdd
integration.test.js (failing test)
import React from "react";
import { mount } from "enzyme";
import Root from "Root";
import CommentList from "components/CommentList";
import moxios from "moxios";
beforeEach(() => {
moxios.install();
moxios.stubRequest("http://jsonplaceholder.typicode.com/comments", {
status: 200,
response: [{ name: "Fetched #1" }, { name: "Fetched #2" }]
});
});
it("can fetch a list of comments and display them", () => {
//Render entire app
const wrapped = mount(
<Root>
<CommentList />
</Root>
);
//Find fetchComments button and click it
wrapped.find(".fetch-comments").simulate("click");
wrapped.update();
//Expect to find a list of comments
expect(wrapped.find("li").length).toBe(2);
});
Root.js
import React from "react";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import reducers from "reducers";
import reduxPromise from "redux-promise";
export default ({
children,
store = createStore(reducers, {}, applyMiddleware(reduxPromise))
}) => {
return <Provider store={store}>{children}</Provider>;
};
CommentList.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchComments } from "actions";
class CommentList extends Component {
renderComments() {
console.log(this.props.comments);
return this.props.comments.map(comment => <li key={comment}>{comment}</li>);
}
render() {
const comments = this.renderComments();
//This is actually creating 2 li elements in the test
console.log("render", comments);
return (
<div>
<button className="fetch-comments" onClick={this.props.fetchComments}>
Fetch comments
</button>
<ul>{comments}</ul>
</div>
);
}
}
function mapStateToProps(state) {
return { comments: state.comments };
}
export default connect(
mapStateToProps,
{ fetchComments }
)(CommentList);
actions/index.js
import { FETCH_COMMENTS } from "actions/types";
import axios from "axios";
export function fetchComments() {
const response = axios.get("http://jsonplaceholder.typicode.com/comments");
return {
type: FETCH_COMMENTS,
payload: response
};
}
reducers/comments.js
import { FETCH_COMMENTS } from "actions/types";
export default function(state = [], action) {
switch (action.type) {
case FETCH_COMMENTS:
const comments = action.payload.data.map(comment => comment.name);
return [...state, ...comments];
default:
return state;
}
}
reducers/index.js
import { combineReducers } from "redux";
import commentsReducer from "reducers/comments";
export default combineReducers({
comments: commentsReducer
});
CommentList.test.js (test passing, using mock store)
import React from "react";
import { mount } from "enzyme";
import Root from "Root";
import CommentList from "components/CommentList";
import createMockStore from "utils/createMockStore";
let wrapped, store;
beforeEach(() => {
const initialState = {
comments: ["Comment 1", "Comment 2"]
};
store = createMockStore(initialState);
wrapped = mount(
<Root store={store}>
<CommentList />
</Root>
);
});
afterEach(() => {
wrapped.unmount();
});
it("Creates one li per comment", () => {
expect(wrapped.find("li").length).toBe(2);
});
it("shows text for each comment", () => {
expect(wrapped.render().text()).toEqual("Fetch commentsComment 1Comment 2");
});
It looks like your problem is caused by your moxios request stubbing. I think you need to wait for the response to be returned before calling update() on your wrapper.
beforeEach(() => {
moxios.install()
})
it('can fetch a list of comments and display them', done => {
// Render entire app
const wrapped = mount(
<Root>
<CommentList />
</Root>
)
// Find fetchComments button and click it
wrapped.find('.fetch-comments').simulate('click')
moxios.wait(() => {
let request = moxios.requests.mostRecent()
request
.respondWith({
status: 200,
response: [{ name: 'Fetched #1' }, { name: 'Fetched #2' }]
})
.then(function () {
wrapped.update()
expect(wrapped.find('li').length).toBe(2)
done()
})
})
})

Print value from props, which is delivered to the component from redux by mapStateToProps

Problem:
I can't display the value from the state of redux, which is delivered by mapStateToProps function to the component.
Project structure:
Create-react-app CLi application built the project.
Inside of the src/ I have the following code structure
Necessary code:
The main page which we are interacting with looks like this:
Underneath it is planned to post the result of the clicking on the buttons.
So how do I bind the redux state and actions to those two components: Calculator and ResultLine?
Let me show the index.js code, where I create the store:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from './reducers/';
import App from './components/App';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.getElementById("root")
);
There are only three actions:
import {CALCULATE, ERASE, PUT_SYMBOL} from "./types";
export const putSymbol = (symbol) => {
return {
type: PUT_SYMBOL,
payload: symbol
}
};
export const calculate = () => {
return {
type: CALCULATE
}
};
export const erase = () => {
return {
type: ERASE
}
};
And in the App.js I pass reducers, which are binded to those actions to the Calculator component:
import React, {Component} from 'react';
import Calculator from './Calculator';
import ResultLine from "./ResultLine";
import {calculate, erase, putSymbol} from "../actions/index";
import {connect} from "react-redux";
class App extends Component {
render() {
return (
<div>
<Calculator
onSymbolClick={this.props.onSymbolClick}
onEqualsClick={this.props.onEqualsClick}
onEraseClick={this.props.onEraseClick}/>
<br/>
<ResultLine result={this.props.result}/>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('mapState', state.calc.line);
return {
result: state.line
}
};
const mapDispatchToProps = {
onSymbolClick: putSymbol,
onEqualsClick: calculate,
onEraseClick: erase
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
And that works fine. Whenever I click the button the state changes, and I observe it in the console log, called in mapStateToProps function.
So I expect, that I can deliver result prop to the Result line easily, and I pass it into the ResultLine component as a parameter. So, let's look at that element:
import React from 'react';
const ResultLine = ({result}) => {
return (
<p>{result}</p>
);
};
export default ResultLine;
And I can see no changes in a result line. Maybe, something wrong with the React/Redux lifecycle management and ResultLine component just does not update on changes in state?
There's an error on mapStateToProps.
Instead of:
const mapStateToProps = (state) => {
return {
result: state.line
}
}
Please use:
const mapStateToProps = (state) => {
return {
result: state.calc.line // calc was missing here
}
}

Categories