TypeError: Cannot read property 'pathname' of undefined in react/redux testing - javascript

I'm testing some react components, a basic tests suite just to know if a component is rendering and their childs.
I'm using redux-mock-store to make the store and {mount} enzyme to mount the container in a provider, but even mocking the correct store this error is always fired:
TypeError: Cannot read property 'pathname' of undefined
Here is my very deadly basic test:
import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import App from '../containers/App.container';
describe('App', () => {
let wrapper;
const mockStore = configureStore([]);
const store = mockStore({
router: {
location: { pathname: '/home', query: {}, search: '' },
params: {}
}
});
console.log(store.getState());
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<App />
</Provider>
);
});
it('Should render app and container elements', () => {
expect(wrapper.find('.app').exists()).toBeTruthy();
expect(wrapper.find('.container').exists()).toBeTruthy();
});
it('Should render the navbar', () => {
expect(wrapper.find('nav').exists()).toBeTruthy();
});
});
And the (even more) simple component / container:
import React, { Component } from 'react';
import NavBar from '../components/Navbar';
class App extends Component {
render() {
const { location, logout} = this.props;
console.log(location);
return (
<section className='app'>
<NavBar location={location.pathname} onLogoutClick={logout}/>
<div className='container'>
{this.props.children}
</div>
</section>
);
}
}
export default App;
Container:
import { connect } from 'react-redux';
import { signOut } from '../actions/auth.actions'
import App from '../components/App';
const mapStateToProps = (state, ownProps) => {
return {
location: ownProps.location
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
logout: () => {
dispatch(signOut())
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
I can't figure out the problem of the test, the mockStore is in the correct format:
Any idea?
Update:
Thinking about it, I have no reducer / prop in the rootReducer for the location, but, i just want to pass down through the children components the location object properties that react-redux-router make available in the ownProps argument.
Weird fact: logging the location property in the app returns me the correct object.
In the tests, is always undefined... (as the error shows).
Here is my rootReducer:
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import { routerReducer } from 'react-router-redux';
import authReducer from './auth.reducer';
import analysisReportsReducer from './AnalysisReports.reducer';
import titleAnalysisReducer from './TitleAnalysis.reducer';
import postsReportsReducer from './PostsReports.reducer';
const rootReducer = combineReducers({
form: formReducer,
routing: routerReducer,
auth: authReducer,
analysis: titleAnalysisReducer,
analysis_reports: analysisReportsReducer,
posts: postsReportsReducer
});
export default rootReducer;

It looks like your location object is scoped beneath the router.
Your test may be grabbing the window.location property, which your test suite may not replicate, assuming the test is cli and not in a browser.
Perhaps try:
<NavBar location={this.props.router.location.pathname} onLogoutClick={logout}/>

Related

unit test react container component

I am trying to unit test React container component using chai/sinon. This is how the component is defined:
import { connect } from 'react-redux';
import React, { Component } from 'react';
class TestComponent extends Component {
componentDidMount() {
const { loadData } = this.props;
loadData();
}
render() {
const { links, heading } = this.props;
return (
<p>
<h1>{heading}</h1>
<div>{links.map(link => link.label)}</div>
</p>
);
}
}
const mapStateToProps = state => ({
heading: state.heading,
links: state.links
});
const mapDispatchToProps = dispatch => ({
loadData: dispatch('LOAD_DATA')
});
export default connect(mapStateToProps, mapDispatchToProps)(TestComponent);
I am writing a unit test to assert that container component has the props loadData, heading and links. Below is the unit test:
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import React from 'react';
import { shallow } from 'enzyme';
import TestContainer from './TestContainer';
const mockStore = configureStore({});
describe('TestContainer', () => {
const initialState = {
heading: 'A test component',
links: []
};
const store = mockStore(initialState);
it(
'renders correctly',
sinon.test(() => {
const component = shallow(
<Provider store={store}>
<TestContainer />
</Provider>
);
console.log(component.prop('heading')); // prints undefined
expect(component.prop('heading')).to.equal('A test component'); // gives AssertionError: expected undefined to equal 'A test component'
})
);
});
Above unit test fails and I get all props as undefined. Can somebody explain what am I doing wrong OR how a container component props should be asserted?
Thanks.

New Components in Application cannot connect to redux

I have created a small application and connected it to Redux. Unfortunately when creating new components and using the same exact code those new components cannot seem to connect to redux and get undefined when accessing it (using mapStateToProps).
I have tried to create new Components and connect them again to no avail. I'm kind of at loss as to why it isn't working especially since the rest of the application can connect and get the state properly
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>
, document.getElementById('root'));
store.js:
const initialState = {
guessedTimezone: '',
timezone: '',
pseudo: '',
};
function rootReducer(state = initialState, action) {
console.log(action);
if (action.type === 'CHANGE_TIMEZONE') {
return Object.assign({}, state, {
timezone: action.timezone,
guessedTimezone: action.guessedTimezone
})
}
if (action.type === 'CHANGE_PSEUDO') {
return Object.assign({}, state, {
pseudo: action.pseudo,
token: action.token
})
}
return state;
}
export default rootReducer;
new Component not connecting:
import React, { Component } from 'react'
import { connect } from 'react-redux'
export class TestPseudo extends Component {
render() {
console.log(this.props.pseudo);
return (
<div>
{this.props.pseudo}
</div>
)
}
}
const mapStateToProps = state => {
return {
pseudo: state.pseudo
}
}
export default connect(mapStateToProps)(TestPseudo)
Here for example this.props.pseudo returns undefined when, if the connection happens, it should return the value if i understand it correctly and yet it shows undefined
EDIT:
App.js as per requested :
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Homepage from './Components/Homepage';
import moment from 'moment';
import moment_timezone from 'moment-timezone';
import HeaderApp from './Components/HeaderApp';
import { TestPseudo } from './Components/TestPseudo';
export class App extends Component {
async componentDidMount() {
let tz = moment.tz.guess(true);
let date = moment(new Date()).local();
let timezone = date['_i'].toString().split('(')[1].split(')')[0];
this.props.dispatch({
type: 'CHANGE_TIMEZONE',
guessedTimezone: tz,
timezone: timezone
})
console.log(`Guessed timezone: ${tz} (${timezone})`);
}
_showHomepage() {
if (this.props.showHomepage && this.props.loaded) {
return (
<div style={styles.mainWindow}>
{/*<Homepage click={this._handleClick} />*/}
<TestPseudo />
</div>
)
}
}
_showHeader() {
return (
<div>
<HeaderApp />
</div>
)
}
render() {
return (
<div>
{this._showHeader()}
{this._showHomepage()}
</div>
)
}
}
const styles = {
mainWindow: {
height: '100vh',
width: '100vw'
}
}
const mapStateToProps = state => {
return {
guessedTimezone: state.guessedTimezone,
timezone: state.timezone,
};
};
export default connect(mapStateToProps)(App);
I call that new Component instead of my old Component. The homepage can connect but not the new one so i think it's not a problem of emplacement
I think its here
import { TestPseudo } from './Components/TestPseudo';
You are importing the non-connected component. Try this
import TestPseudo from './Components/TestPseudo';
For your understanding, exporting as default can be imported like so;
export default Component
import WhateverName from ....
Named export like const or in your case class;
export class Component
import { Component } from ...
So use brackets when Named, and skip brackets when default.

Redux action not triggering reducers

Problem
I wired up my react application with a Redux store, added an api action to gather data from my backend including middleware redux-promise. Most everything seems to work as I can see my store in the React web editor along with the combine reducer keys. When I have my action called, it works and console logs the completed promise. However, my reducers never run. I thought it was an issue with my dispatch on the main container, but I've tried every way that I can think of at this point - regular dispatch() and bindActionCreators. HELP!
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import promiseMiddleware from 'redux-promise';
import RootReducer from './reducers';
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware)(createStore)
let store = createStore(RootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));`
Combine Reducers
import { combineReducers } from 'redux';
import ReducerGetPostings from './reducer_get_postings'
const rootReducer = combineReducers({
postingRecords: ReducerGetPostings
})
export default rootReducer;
Reducer
import { FETCH_POSTINGS } from '../actions/get_postings'
export default function (state = null, action) {
console.log('action received', action)
switch (action.type) {
case FETCH_POSTINGS:
return [ action.payload ]
}
return state;
}
Action API
import axios from 'axios';
import { url } from '../api_route';
export const FETCH_POSTINGS = 'FETCH_POSTINGS'
export function fetchpostings() {
const postingRecords = axios.get(`${url}/api/postings`)
console.log('Postings', postingRecords)
return {
type: FETCH_POSTINGS,
payload: postingRecords
};
}
Container
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchpostings } from '../../actions/get_postings.js'
class Dashboard extends Component {
//....lots of other functionality already built here.
componentDidMount() {
axios.get(`${url}/api/postings`)
.then(res => res.data)
.then(
(postingRecords) => {
this.setState({
postingData: postingRecords,
postingOptions: postingRecords
});
},
(error) => {
this.setState({
error
})
}
)
// primary purpose is to replace the existing api call above with Redux Store and fetchpostings action creator
fetchpostings()
}
}
function mapDispatchToProps(dispatch) {
// return {actions: bindActionCreators({ fetchpostings }, dispatch)}
return {
fetchpostings: () => dispatch(fetchpostings())
}
}
export default connect(null, mapDispatchToProps)(Dashboard);
You are not dispatching your action, when you call fetchpostings() in componentDidMount you are calling the method imported from actions/get_postings.js, not the method that will dispatch.
Try this.props.fetchpostings() instead.
You also did not bind state to props you need to do that as well.

How to access redux variables and functions from deep components

I'm a bit confused about redux implementation.
Let's say my app has this component structure:
-App
--ProfilationStep
---ProfilationStep1
----React-Select (http://jedwatson.github.io/react-select/)
I need to use redux because the app is going to grow bigger and deeper, so I started by setting up Actions, Reducers and Action types for the React-Select component. I also set the mapStateToProps in the App.js file.
Now I need to know how to pass/access the data stored in redux to other components (React-Select for example) and how to edit it with the actions I declared.
This is my index.js file
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import ProfilationSelectReducer from './components/reducers/profilationSelect';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
const store = createStore(
ProfilationSelectReducer
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
registerServiceWorker();
This is my App.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import { bindActionCreators} from 'redux'
import Profilation from './components/Profilation'
import ProfilationStep from './components/Profilation/ProfilationStep'
import { connect } from 'react-redux';
import * as SelectActionCreators from './components/actions/profilationSelect'
import 'react-select/dist/react-select.css';
class App extends Component {
static propTypes = {
steps: PropTypes.array.isRequired
};
render() {
console.log(this.props)
const { dispatch, steps } = this.props;
const changeValue= bindActionCreators(SelectActionCreators.changeValue, dispatch);
const stepComponents = this.props.steps.map((step, index) => (
<ProfilationStep
key={index}
index={index}
step={step}
/>
));
return (
<div className="repower-app">
{ stepComponents }
</div>
);
}
}
const mapStateToProps = state => ({
steps:state.steps
});
export default connect(mapStateToProps)(App);
This is my ProfilationStep.js file
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import ProfilationStep1 from './ProfilationStep1'
import ProfilationStep2 from './ProfilationStep2'
import ProfilationStep3 from './ProfilationStep3'
import ProfilationStep4 from './ProfilationStep4'
import ProfilationStep5 from './ProfilationStep5'
const ProfilationStep = props =>
<div className='ProfilationStep'>
{props.index===0 &&
<ProfilationStep1
step={props.step}
/>
}
{props.stepIndex===2 &&
<ProfilationStep2
handleSelect={props.handleSelect}
handleInput={props.handleInput}
expend={props.expend}
period={props.period}
light={props.light}
gas={props.gas}
/>
}
{props.stepIndex===3 &&
<ProfilationStep3
handleSelect={props.handleSelect}
environment={props.environment}
/>
}
{props.stepIndex===4 &&
<ProfilationStep4
flexibility={props.flexibility}
handleSelect={props.handleSelect}
/>
}
{props.stepIndex===5 &&
<ProfilationStep5
customize={props.customize}
handleSelect={props.handleSelect}
/>
}
</div>
export default ProfilationStep
This is my ProfilationStep1.js file
import React, { Component } from 'react';
import Select from 'react-select';
import PropTypes from 'prop-types'
var jobOptions = [
{ value: 'edilizia', label: 'Edilizia' },
{ value: 'editoria', label: 'Editoria' },
{ value: 'educazione', label: 'Educazione' }
];
const ProfilationStep1 = props =>
<div className='ProfilationStep'>
La mia attività si occupa di <Select
name="job"
value={props.step.job}
onChange={e => props.changeValue(e.target.value)}
options={jobOptions}
/>
</div>
ProfilationStep1.propTypes = {
//isComplete: PropTypes.bool.isRequired,
//isActive: PropTypes.bool.isRequired
job: PropTypes.string.isRequired,
service: PropTypes.string.isRequired,
handleSelect: PropTypes.func.isRequired
}
export default ProfilationStep1
This is my reducer
import * as ProfilationSelectActionTypes from '../actiontypes/profilationSelect';
const initialState = {
steps: [{
job: "",
service: ""
}],
}
export default function ProfilationSelectReducer (state=initialState, action){
switch(action.type){
case ProfilationSelectActionTypes.CHANGE_VALUE:
return {
...state,
steps:[{
job: action.value
}]
};
default:
return state;
}
}
This is my actiontypes file
export const CHANGE_VALUE ='profilationSelect/CHANGE_VALUE';
and, finally, this is my actions file
import * as ProfilationSelectActionTypes from '../actiontypes/profilationSelect';
export const changeValue = value =>{
return{
type: ProfilationSelectActionTypes.CHANGE_VALUE,
value
}
}
Thank you for any help
You are definitely on the right way.
The solution is simple: You bind your state to the react props. With the props, you can do whatever you like (e.g. pass them to react-select). If you want to modify it, you have to map "mapDispatchToProps", where you map functions, which execute your actions to the props. This works the same as "mapStateTopProps":
End of App.js (import your actions file on top, named "profilationSelectActions" here):
const mapStateToProps = state => ({
steps:state.steps
});
const mapDispatchToProps = dispatch => ({
updateJobValue: (value) => dispatch(profilationSelectActions.changeValue(value))
}
// Also add here mapDispatchToProps
export default connect(mapStateToProps, mapDispatchToProps)(App);
Now the function "updateJobValue" is available in the props of your app.js. You can now easily pass it down to your components and to the onChange event of react-select:
In your ProfilationStep1.js change this line:
onChange={e => props.changeValue(e.target.value)}
To this (after you passed the function updateJobValue down)
onChange{e => props.updateJobType(e.target.value)}
After that, updateJobType should go all the way up to App.js and then dispatch the action. After that, the application will re-render with the new steps.

Test connected component in React/Redux

I am trying test my connected component of my React/Redux app and I wrote some test case which actually throws the error:
App component › shows account info and debits and credits`
Invariant Violation: Could not find "store" in either the context or props of "Connect(AccountInfo)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(AccountInfo)".
The test case which trow an error app.test.js is below. And my problem is that I don't understand what should I wrap here by Connect() because I didn't use AccountInfo here:
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import App from './App';
import * as actions from '../../actions';
function setup() {
const props = {
errorMessage: null,
actions
};
const enzymeWrapper = mount(<App {...props} />);
return {
props,
enzymeWrapper,
};
}
describe('App component', () => {
it('shows account info and debits and credits`', () => {
const {enzymeWrapper} = setup();
expect(enzymeWrapper.find('.account-info').exists()).toBe(true);
expect(enzymeWrapper.find('.debits-and-credits').exists()).toBe(true);
});
it('shows error message', () => {
const {enzymeWrapper} = setup();
enzymeWrapper.setProps({ errorMessage: 'Service Unavailable' });
expect(enzymeWrapper.find('.error-message').exists()).toBe(true);
});
});
My containers/app.js:
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../actions';
import AppComponent from '../components/App/App';
const mapStateToProps = state => ({
isFetching: state.balance.isFetching,
errorMessage: state.errorMessage,
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actions, dispatch),
});
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(AppComponent);
export default AppContainer;
The component app.js:
import React, { Component } from 'react';
import ErrorMessage from '../../containers/ErrorMessage';
import AccountInfo from '../../containers/AccountInfo';
import DebitsAndCredits from '../../containers/DebitsAndCredits';
import './App.css';
const AppComponent = () =>
<div className="app">
<AccountInfo />
<DebitsAndCredits />
</div>;
export class App extends Component {
componentWillMount() {
const { actions } = this.props;
actions.fetchBalance();
}
render() {
const { errorMessage } = this.props;
return errorMessage ? <ErrorMessage /> : <AppComponent />;
}
}
export default App;
UPD:
I updated my test case and now it looks like:
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import createSagaMiddleware from 'redux-saga';
import { initialState } from '../../reducers/balance/balance';
import App from './App';
import * as actions from '../../actions';
const middlewares = [createSagaMiddleware];
const mockStore = configureMockStore(middlewares);
const store = mockStore(initialState);
function setup() {
const props = {
errorMessage: null,
actions,
};
const enzymeWrapper = mount(
<Provider store={store}>
<App {...props} />
</Provider>
);
return {
props,
enzymeWrapper,
};
}
describe('App component', () => {
it('shows account info and debits and credits`', () => {
const { enzymeWrapper } = setup();
expect(enzymeWrapper.find('.account-info').exists()).toBe(true);
expect(enzymeWrapper.find('.debits-and-credits').exists()).toBe(true);
});
it('shows error message', () => {
const { enzymeWrapper } = setup();
enzymeWrapper.setProps({ errorMessage: 'Service Unavailable' });
expect(enzymeWrapper.find('.error-message').exists()).toBe(true);
});
});
And my error now is:
App component › shows account info and debits and credits`
TypeError: Cannot read property 'account' of undefined
UPD 2:
My initialState which I put when I create mocked store:
const initialState = {
isFetching: false,
account: {},
currency: '',
debitsAndCredits: [],
};
My AccountInfo component:
import React from 'react';
const AccountInfo = ({ account, currency }) =>
<header className="account-info">
<p>{account.name}</p>
<p>
IBAN: {account.iban}<br />
Balance: {account.balance}<br />
Currency: {currency}<br />
</p>
</header>;
export default AccountInfo;
For testing the connected component, you need to mock the provider as well, since the connect picks state variables from redux store.
Do this
const enzymeWrapper = mount (<Provider store={mockStore}><App {...props}/></Provider>)
You need to mock the redux store too.
Edit 1:
Just looking at your AccountInfo component it tells me that you are expecting account in the props here.
AccountInfo = ({account}) =>
So that means App.js has to pass down the accounts' value in the props. Same thing goes for currency.

Categories

Resources