How to test nested connected components using Enzyme? - javascript

I am trying to test nested connected components (Redux connected):
The Main component has Container component and are provided by store wrapper in App.
import React, { Component } from 'react';
import Main from './components/Main';
import './App.css';
import { Provider } from 'react-redux'
import createStore from './redux'
import applyConfigSettings from './config'
// Apply config overrides
applyConfigSettings()
const store = createStore()
class App extends Component {
render() {
return (
<Provider store={store}>
<Main/>
</Provider>
);
}
}
export default App;
Main.js
import React, { Component } from 'react';
import logo from '../logo.svg';
import { connect } from 'react-redux'
import Container from './Container'
export class Main extends Component {
render(){
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to Pomodoro App</h1>
</header>
<Container/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
fetching: state.user.fetching,
}
}
const mapDispatchToProps = (dispatch) => {
return {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Main)
Container.js
import React, { Component } from 'react';
import Grid from 'material-ui/Grid';
import { connect } from 'react-redux'
export class Container extends Component{
render(){
return (
<div className="grid-root">
</div>
)
}
}
const mapStateToProps = (state) => {
return {
fetching: state.user.fetching,
}
}
const mapDispatchToProps = (dispatch) => {
return {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Container)
All this is created using Create-react-app library. I have installed Enzyme too for testing. Here is my test file for Main.test.js
import React from 'react';
import { shallow, mount } from 'enzyme';
import {Main} from '../../components/Main'
import ContainerConnect, {Container} from '../../components/Container';
import configureStore from 'redux-mock-store';
import { Provider, connect} from 'react-redux';
describe('Main', () => {
let wrapper;
let mountWrapper;
it('wraps all the contents in a div with .App class', () => {
wrapper = shallow(<Main />);
expect(wrapper.find('.App').length).toEqual(1);
});
it('wraps content of header in a div with .App-header class', () => {
wrapper = shallow(<Main />);
expect(wrapper.find('.App-header').length).toEqual(1);
});
it('mount', () => {
const middlewares = [];
const mockStore = configureStore(middlewares);
const initialState = {}
const store = mockStore(initialState)
mountWrapper = mount(
<Provider store={store}>
<Main />
</Provider>
);
});
})
I get following error for the last test:
FAIL src/tests/components/Main.test.js
● Console
console.error node_modules/react-dom/cjs/react-dom.development.js:9643
The above error occurred in the <Connect(Container)> component:
in Connect(Container) (at Main.js:16)
in div (at Main.js:11)
in Main (at Main.test.js:31)
in Provider (created by WrapperComponent)
in WrapperComponent
Consider adding an error boundary to your tree to customize error handling behavior.
Visit some link to fb to learn more about error boundaries.
● Main › mount
TypeError: Cannot read property 'fetching' of undefined
at Function.mapStateToProps [as mapToProps] (src/components/Container.js:18:30)
at mapToPropsProxy (node_modules/react-redux/lib/connect/wrapMapToProps.js:54:92)
at Function.detectFactoryAndVerify (node_modules/react-redux/lib/connect/wrapMapToProps.js:63:19)
at mapToPropsProxy (node_modules/react-redux/lib/connect/wrapMapToProps.js:54:46)
at handleFirstCall (node_modules/react-redux/lib/connect/selectorFactory.js:37:18)
at pureFinalPropsSelector (node_modules/react-redux/lib/connect/selectorFactory.js:85:81)
at Object.runComponentSelector [as run] (node_modules/react-redux/lib/components/connectAdvanced.js:43:25)
at Connect.initSelector (node_modules/react-redux/lib/components/connectAdvanced.js:195:23)
at new Connect (node_modules/react-redux/lib/components/connectAdvanced.js:136:15)
at constructClassInstance (node_modules/react-dom/cjs/react-dom.development.js:6801:20)
at updateClassComponent (node_modules/react-dom/cjs/react-dom.development.js:8336:9)
at beginWork (node_modules/react-dom/cjs/react-dom.development.js:8982:16)
at performUnitOfWork (node_modules/react-dom/cjs/react-dom.development.js:11814:16)
at workLoop (node_modules/react-dom/cjs/react-dom.development.js:11843:26)
at renderRoot (node_modules/react-dom/cjs/react-dom.development.js:11874:9)
at performWorkOnRoot (node_modules/react-dom/cjs/react-dom.development.js:12449:24)
at performWork (node_modules/react-dom/cjs/react-dom.development.js:12370:9)
at performSyncWork (node_modules/react-dom/cjs/react-dom.development.js:12347:5)
at requestWork (node_modules/react-dom/cjs/react-dom.development.js:12247:7)
at scheduleWorkImpl (node_modules/react-dom/cjs/react-dom.development.js:12122:13)
at scheduleWork (node_modules/react-dom/cjs/react-dom.development.js:12082:12)
at scheduleRootUpdate (node_modules/react-dom/cjs/react-dom.development.js:12710:5)
at updateContainerAtExpirationTime (node_modules/react-dom/cjs/react-dom.development.js:12738:12)
at Object.updateContainer (node_modules/react-dom/cjs/react-dom.development.js:12765:14)
at ReactRoot.Object.<anonymous>.ReactRoot.render (node_modules/react-dom/cjs/react-dom.development.js:16069:15)
at node_modules/react-dom/cjs/react-dom.development.js:16488:14
at Object.unbatchedUpdates (node_modules/react-dom/cjs/react-dom.development.js:12557:12)
at legacyRenderSubtreeIntoContainer (node_modules/react-dom/cjs/react-dom.development.js:16484:17)
at Object.render (node_modules/react-dom/cjs/react-dom.development.js:16543:12)
at Object.render (node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:218:50)
at new ReactWrapper (node_modules/enzyme/build/ReactWrapper.js:98:16)
at mount (node_modules/enzyme/build/mount.js:19:10)
at Object.it (src/tests/components/Main.test.js:29:42)
at new Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:118:7)
Is it so hard to test nested connected components? I have to go and use more of it going forward in the application as well. And also does this mean that testing button clicks and all is harder to test as well?

Based on what you are trying to test, you should not have to deal with redux at all.
Since you are testing your <Main /> component and whether it renders the header properly, just mock your <Container /> completely:
jest.mock('../../components/Container', () => ()=> <div id="mockContainer">
mockContainer
</div>);
Then you can shallow mount your <Main /> without worrying about redux.
After that, when you start unit testing your <Container />, just unit test the React.Component class and do not bother with the connected Component, testing it is redundant.
In other words, you should be able to test any React app without ever having to mock the redux store. Just mock connected components entirely.
More info here

Related

Jest Store is Mocked -- Not detecting it?

I have some code I've pulled from another project I had done. I am moving the store here and passing the App as a child component.
Unfortunately I receive this error. I'm not sure why this doesn't work.
could not find react-redux context value; please ensure the component is wrapped in a <Provider>
29 |
30 | function App() {
> 31 | const { userData } = useSelector(state => state.user)
utils.js
import { Provider } from 'react-redux'
import store from "../redux/store"
const ReduxProvider = ({ children }) => (
<Provider store={store}>{children}</Provider>
)
export {
ReduxProvider
}
App.test.js
import { render } from '#testing-library/react';
import { ReduxProvider } from './utils'
import App from '../App';
test('renders', () => {
render(
<ReduxProvider>
<App />
</ReduxProvider>
);
});

problem connecting multiple container components to redux store

I'm having trouble connecting my container components to the redux store, I'm not sure exactly where the connection is supposed to happen, whether in the container component or the component that will be dispatching an action. currently, my index.js looks like this
import React from "react";
import reactDOM from "react-dom";
import App from "./app.jsx";
import storeFactory from "./store";
import { Provider } from 'react-redux';
const store = storeFactory();
reactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("app")
);
currently, my store factory function looks like this
import rootReducer from "../reducers";
import { createStore, applyMiddleware } from "redux";
import { fetchProductInformation } from "../actions";
const storeData = {
productInformation: {}
};
const storeFactory = (initialState = storeData) => {
applyMiddleware(fetchProductInformation)(createStore)(
rootReducer,
localStorage["redux-store"]
? JSON.parse(localStorage["redux-store"])
: storeData
);
};
export default storeFactory;
my container component is
import SearchBar from '../components/searchbar.jsx';
import Nutrients from "../components/Nutrients.jsx";
import { fetchProductInformation } from '../actions';
import { connect } from 'react-redux';
const newSearch = (props) => (
<SearchBar
className="searchbar searchbar_welcome"
onNewProduct={( name ) => (props.fetchProductInformation(name))}
/>
)
const productInformation = (props) => {
const { nutrients, name } = props;
return nutrients.length > 1 ?
(
<div>
<newSearch />
<h3>{name}</h3>
<hr/>
<Nutrients
className="nutrientInformation"
list={nutrients}
/>
</div>
)
: null
}
const mapStateToProps = ({nutrients, name}) => ({
nutrients,
name
});
const mapDispatchToProps = dispatch => ({
fetchProductInformation: name => {
dispatch(fetchProductInformation(name))
}
});
export const Search = connect(null, mapDispatchToProps)(newSearch);
export const productInfo = connect(mapStateToProps)(productInformation);
when i run the code i get the following error
Provider.js:19 Uncaught TypeError: Cannot read property 'getState' of undefined
at Provider.js:19
at mountMemo (react-dom.development.js:15669)
at Object.useMemo (react-dom.development.js:15891)
at useMemo (react.development.js:1592)
at Provider (Provider.js:18)
at renderWithHooks (react-dom.development.js:15108)
at mountIndeterminateComponent (react-dom.development.js:17342)
at beginWork$1 (react-dom.development.js:18486)
at HTMLUnknownElement.callCallback (react-dom.development.js:347)
at Object.invokeGuardedCallbackDev (react-dom.development.js:397)
react-dom.development.js:19814 The above error occurred in the <Provider> component:
in Provider
Consider adding an error boundary to your tree to customize error handling behavior.
Visit react-error-boundaries to learn more about error boundaries.
Provider.js:19 Uncaught TypeError: Cannot read property 'getState' of undefined
at Provider.js:19
at mountMemo (react-dom.development.js:15669)
at Object.useMemo (react-dom.development.js:15891)
at useMemo (react.development.js:1592)
at Provider (Provider.js:18)
at renderWithHooks (react-dom.development.js:15108)
at mountIndeterminateComponent (react-dom.development.js:17342)
at beginWork$1 (react-dom.development.js:18486)
at HTMLUnknownElement.callCallback (react-dom.development.js:347)
at Object.invokeGuardedCallbackDev (react-dom.development.js:397)
from the errors shown i dont know exactly what the error is as it seems to be comming from the provider.js..
your code has some bug. here are fixed code.
import SearchBar from '../components/searchbar.jsx';
import Nutrients from "../components/Nutrients.jsx";
import { fetchProductInformation } from '../actions';
import { connect } from 'react-redux';
const newSearch = (props) => (
<SearchBar
className="searchbar searchbar_welcome"
onNewProduct={( name ) => (props.fetchProductInformation(name))}
/>
)
const productInformation = (props) => {
const { nutrients, name } = props;
return nutrients.length > 1 ?
(
<div>
<newSearch />
<h3>{name}</h3>
<hr/>
<Nutrients
className="nutrientInformation"
list={nutrients}
/>
</div>
)
: null
}
const mapStateToProps = ({nutrients, name}) => ({
nutrients,
name
});
const mapDispatchToProps = dispatch => ({
fetchProductInformation: name => {
dispatch(fetchProductInformation(name))
}
});
export const Search = connect(null, mapDispatchToProps)(newSearch);
export const productInfo = connect(mapStateToProps)(productInformation)
store.js
import rootReducer from "../reducers";
import { createStore } from "redux";
const storeData = {
productInformation: {}
};
const initialStore = localStorage["redux-store"] ? JSON.parse(localStorage["redux-store"]) : storeData;
const store = createStore(rootReducer, initialStore);
export default store;
index.js
import store from "./store";
...
<Provider store={store}>
...

how to export a react-redux project as a node_module

I'm trying to export a redux project as a node_module that has an index.js shown below (simplified):
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import App from './App.jsx';
const middlewares = [thunk.withExtraArgument(), promiseMiddleware()];
const middlewareEnhancer = applyMiddleware(...middlewares);
const preloadedState = {};
const store = createStore(
rootReducer,
preloadedState,
middlewareEnhancer
);
const ExampleModule = (props) => {
return (
<Provider store={store}>
<App />
</Provider>
);
};
export default ExampleModule;
In my main application:
...
import ExampleModule from 'example-module';
class Application extends React.Component {
render() {
return <ExampleModule />;
}
}
function mapStateToProps({ state }) {
return {
state: state
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(require('..').actions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Application);
This throws an error:
bundle.js:349 Uncaught Invariant Violation: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(App)"
I'm assuming it's because this essentially creates nested <Providers> which is against Redux's methodology of one store.
My question would be what would be the best way to go about publishing a node_module that has a redux store in it?
Found the answer here:
https://redux.js.org/recipes/isolating-redux-sub-apps
It keeps the store local to the component.

Using custom mocks for components with jest

I'm writing a React-Native application in which I have a screen I need to test:
MyScreen.js
import React, { Component } from "react";
import CustomTable from "./CustomTable";
export default MyScreen extends Component {
render() {
return <CustomTable />;
}
}
CustomTable.ios.js
import React, { Component } from "react";
import { View } from "react-native";
import TableView from "react-native-tableview";
export default MyScreen extends Component {
render() {
return (
<View>
...some stuff
<TableView />
</View>
);
}
}
react-native-tableview calls some iOS specific code so I have mocked it out by simply returning the android version (CustomTable.android.js) in the __mocks__ folder
__mocks__/CustomTable.ios.js
import CustomAndroidTable from "../CustomTable.android";
export const CustomTable = CustomAndroidTable;
What I want to do is test MyScreen.js with Jest, but I want it to use the __mock__/CustomTable.ios. How do I go about getting it to do that? Is that even possible with Jest? My current test file looks like:
tests/MyScreen.test.js
import React from "react";
import renderer from "react-test-renderer";
import MyScreen from "../src/MyScreen";
describe("test", () => {
it("works", () => {
jest.mock("../src/CustomTable.ios");
const tree = renderer.create(
<MyScreen />,
).toJSON();
expect(tree).toMatchSnapshot();
});
But it still calls the original version of CustomTable.ios. What am i doing wrong?
You should call jest.mock outside of your suite test. It should be immediately after your imports.
import React from "react";
import renderer from "react-test-renderer";
import MyScreen from "../src/MyScreen";
jest.mock("../src/CustomTable.ios");
describe("test", () => {
it("works", () => {
const tree = renderer.create(
<MyScreen />,
).toJSON();
expect(tree).toMatchSnapshot();
});

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

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}/>

Categories

Resources