How to test two redux related components? - reactjs

Suppose there are two components, one is action dispatching class, other one is a state receiving class.
Component1 has a method dispatch a action to change props1 redux state, then Component2 receives the props1 from redux state passing it to Component3.
Component1 and Component2 are combined under SuperComponent.
// Component1.js
class Component1 extends React.component {
method1 = () => {
this.props.action1({ props1: something });
}
render() {
return (
...
)
}
}
export default connect(
state => ({ ... }),
{action1}
)(Component1)
// Component2.js
class Component2 extends React.component {
render() {
return (
<Component3 props1={this.props.props1} />
)
}
}
export default connect(
state => ({ props1: state.reducer1.props1 }),
{}
)(Component2)
const Component3 = ({ props1 }) => (
<div props1={props1} >
...
</div>
)
// SuperComponent
const SuperComponent = () => {
<div>
<Component1>
<Component2>
</div>
}
How to test this logic by jest and enzyme? Using integrate testing or separated unit testing? Thanks in advance.

You should test each component separately for unit testing.
Component1: You should test that when you click a button or something, that Component1 is dispatching the right action.
Component2:, you should test that when you provide the props, it behaves as excepted.
You can export the Component2 separately to unit test it.
// Component2.js
export class Component2 extends React.component {
render() {
return (
<Component3 props1={this.props.props1} />
)
}
}
export default connect(
state => ({ props1: state.reducer1.props1 }),
{}
)(Component2)

Related

React - Test separate parent component (without redux)

I wanna test parent component, but I want to do this without redux. I have problem because I've got error:
Invariant Violation: Could not find "store" in either the context or props of "Connect(MarkerList)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(MarkerList)".
My parent component:
export class Panel extends React.Component {
constructor(props) {
}
componentDidUpdate(prevProps) {
...
}
handleCheckBox = event => {
...
};
switchPanelStatus = bool => {
...
};
render() {
...
}
}
const mapDispatchToProps = {
isPanelSelect
};
export const PanelComponent = connect(
null,
mapDispatchToProps
)(Panel);
My child component:
export class MarkerList extends Component {
constructor(props) {
...
};
}
componentDidMount() {
...
}
componentDidUpdate() {
...
}
onSelect = (marker, id) => {
...
}
render() {
...
}
}
const mapStateToProps = state => ({
...
});
const mapDispatchToProps = {
...
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MarkerList);
Panel test file:
import '#testing-library/jest-dom'
import "#testing-library/react"
import React from 'react'
import {render, fireEvent, screen} from '#testing-library/react'
import {Panel} from '../Panel';
test("test1", async () => {
const isPanelSelect = jest.fn();
const location = {
pathname: "/createMarker"
}
const {getByText} = render( <Panel isPanelSelect={isPanelSelect} location={location} />)
})
I've tried set store as props to Panel component or wrap It via Provider in my test file but It doesn't help me.
react-redux doesn't work without the store. You can either provide it by the context or props (usually in tests). You can provide a mock version in the test. The main problem is that both components require Redux. You have to manually forward the context to the children if it's provided as prop. The alternative solution is to mount your component within a Redux aware tree:
import { Provider } from "react-redux";
test("test1", async () => {
const { getByText } = render(
<Provider store={createFakeStore()}>
<Panel isPanelSelect={isPanelSelect} location={location} />
</Provider>
);
});

React, Redux - pass function from component A to other components

import React from "react";
import OtherComponent from "./OtherComponent";
class Main extends React.Component {
constructor(props) {
super(props);
this.runMyFunction = this.runMyFunction.bind(this);
this.myFunction = this.myFunction.bind(this);
}
runMyFunction(event) {
event.preventDefault();
this.myFunction();
}
myFunction() {
return console.log("I was executed in Main.js");
}
render() {
return (
<div>
<OtherComponent runMyFunction={this.runMyFunction} />
</div>
);
}
}
export default Main;
import React from "react";
class OtherComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.runMyFunction();
}
render() {
return (
<div>
<button onClick={this.handleClick} />Click me to execute function from Main </button>
</div>
);
}
}
export default OtherComponent;
I'm new in redux and don't know how to pass and run that function in other component. It was easy not using redux, just pass as props like in example above.
I have folder with actions, components, containers and reducers.
Now I have Main.js where I have
import React from "react";
const Main = ({data, getData}) => {
const myFunction = () => {
return "ok";
};
return (
<div>
<p>This is main component</p>
</div>
);
};
export default Main;
In MainContainer.js I got:
import Main from "../../components/Main/Main";
import { connect } from "react-redux";
import {
getData
} from "../../actions";
function mapStateToProps(state) {
return {
data: state.main.data
};
}
const mapDispatchToProps = (dispatch) => {
return {
getData: () => dispatch(getData())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Main);
So how I can run function myFunction() in OtherComponent.js:
import React from "react";
const OtherComponent = ({executeFunctionInMainComponent}) => {
return (
<div>
<button onClick={executeFunctionInMainComponent}>run action</button>
</div>
);
};
export default OtherComponent;
I need to just run, not pass whole function, just execute myFunction in Main.js but action to run this function will came from OtherComponent.
So first i have to mention that i believe that you have a misconception of redux. This isn't to allow for functions created in components to be reused in different locations. This is to move that logic to a reducer outside of your function which would allow it to be used wherever you wired it with {connect} from react-redux. So you will need a couple of files (for clarity). First you're going to need an action file which we'll name myReturnOkAction.
export const myReturnOkAction = (/*optional payload*/) => {
return {
type: 'PRINT_OK',
}
}
Redux Actions
This is what you're going to call in your mapDispatchToProps function where you're going to trigger this event. You're going to have to import it into your OtherComponent so import {myReturnOkAction} from "/*wherever the files exists*/" and to include it in your mapDispatchToProps as okFunction: () => dispatch(myReturnOkAction())
Once you have your action your connect Higher Order Component (HOC) wrapping your main component is going to need a Reducer to modify your current store state as well as do any actions.
export const myReturnOkReducer = (state, action) => {
if(action.type === 'PRINT_OK'){
/*This is where you update your global state*/
/*i.e. return {...store, valueWanted: ok}*/
}else{
return state
}
}
Redux Reducers
So the way that this is going to move is that you're function, somewhere is going to call the action. Once the action is called its going to trigger the reducer and make any changes to the store which you need. Once the reducer has updated the store with new values its then going to update any components which are connected to it through the connect HOC which will cause them to re-render with new information.
Also my favorite image to describe how redux works.
I hope this helps.
I found an answer:
I still can pass as props in redux but I can't do this in this way: OtherComponent = ({executeFunctionInMainComponent}) => {}. I need to do in this way: OtherComponent = (props) => {} and then inside that component I have an access via props.executeFunctionInMainComponent

Instance returns NULL for connected component on mount in Jest

I am relatively new to react and apologies for any terms that dont fit the jargon.
I am trying to test a prototype method of a connected component which consists of a ref variable, as below:
app.js
export class Dashboard extends React.Component { // Exporting here as well
constructor(props) {
this.uploadFile = React.createRef();
this.uploadJSON = this.uploadJSON.bind(this);
}
uploadJSON () {
//Function that I am trying to test
//Conditions based on this.uploadFile
}
render() {
return (
<div className="dashboard wrapper m-padding">
<div className="dashboard-header clearfix">
<input
type="file"
ref={this.uploadFile}
webkitdirectory="true"
mozdirectory="true"
hidden
onChange={this.uploadJSON}
onClick={this.someOtherFn}
/>
</div>
<SensorStatList />
<GraphList />
</div>
);
}
const mapStateToProps = state => ({
//state
});
const mapDispatchToProps = dispatch => ({
//actions
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Dashboard);
}
Here, SensorStatList and GraphList are functional components, also connected using redux.
After some research I have my test file to this level:
app.test.js
import { Dashboard } from '../Dashboard';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
const store = configureStore();
export const CustomProvider = ({ children }) => {
return (
<Provider store={store}>
{children}
</Provider>
);
};
describe("Dashboard", () => {
let uploadJSONSpy = null;
function mountSetup () {
const wrapper = mount(
<CustomProvider>
<Dashboard />
</CustomProvider>
);
return {
wrapper
};
}
it("should read the file", () => {
const { wrapper } = mountSetup();
let DashboardWrapper = wrapper;
let instance = DashboardWrapper.instance();
console.log(instance.ref('uploadFile')) // TypeError: Cannot read property 'ref' of null
})
Can someone help me understand why this error
console.log(instance.ref('uploadFile'))
// TypeError: Cannot read property 'ref' of null
pops up? Also, if this approach is fine? If not, what are the better options?
wrapper is CustomProvider which has no instance, and ref is supposed to work with deprecated string refs.
In case a ref should be accessed on Dashboard, it can be:
wrapper.find(Dashboard).first().instance().uploadFile.current
In case input wrapper should be accessed, it can be:
wrapper.find('input').first()

React Redux render connected component

I'm using the connect function React Redux to connect my store to a React component. After it is connected I want to render the component. This works:
class Init extends React.Component {
render() {
return (
<View /> // Is really more elaborate
)
}
}
Connect it to the store:
const InitPage = connect(
state => ({
}),
dispatch => ({
})
)(Init)
Now render it:
class App extends React.Component {
render() {
const content = <InitPage />
return (
{content}
)
}
}
Great. This all works. However...
When I place the connect inside a function and return the connect, as in:
const getInitPage = () => {
const InitPage = connect(
state => ({
}),
dispatch => ({
})
)(Init)
return InitPage
}
If I now try to render the component:
class App extends React.Component {
render() {
const content = getInitPage()
return (
{content}
)
}
}
I get an error:
Invariant Violation: React.Children.only expected to receive a single React element child.
What is the correct way to return a Redux "connected" component from a function?
In case you are returning the component from the function, you need to render it like
class App extends React.Component {
render() {
const Content = getInitPage()
return (
<Content/>
)
}
}
Also make sure the componentName starts with a uppercase character.

Correct place to load data in connected redux component

Where is the correct place to load data in a redux component?
Currently I have it this way.
Say I have this container component:
import { loadResultsPage } from '../actions/winratio-actions';
function mapStateToProps(state) {
const {
isFetching,
results
} = state;
return {
isFetching,
results
};
};
export default connect(mapStateToProps, {
loadResultsPage
})(WinRatio);
I then make a call in the wrapped component's componentWillMount lifecycle event:
export default class WinRatio extends Component {
componentWillMount() {
this.props.loadResultsPage();
}
render() {
return (
<div>
<h1>Win Ratio</h1>
</div>
);
}
};
Where should this call happen?
You can do it in your container component. If you use redux you probably use smart\dumb components strategy. Create a container where you use compose function from redux package and you can compose it like this:
export default compose(
connect(null, { loadData }), //this is your async action
doOnComponentMount(({props}) => props.loadData()),
)(MyDumbComponent)
and doOnComponentMount is:
function doOnComponentMount(cb) {
return (Component) => {
return class extends React.Component {
componentDidMount() {
cb(this);
}
render() {
return <Component {...this.props} />;
}
}
}
}

Resources