React, Enzyme, Redux unit test connected-component in React 16+ - reactjs

I was trying to upgrade to react 16+ and my existing unit tests are failing. The application runs fine, only unit tests fails.
connected-component:
const componentsWrapper = (Components, selectData) => {
class BaseComponent extends Component {
constructor(props) {
super(props);
this.state = {
saveMode: false,
updateMode: false
};
this.foo = () => { };
}
render() {
return (
<Components
{...this.props}
/>
);
}
}
}
const mapDispatchToProps = dispatch => {
return selectData.getDispatch(dispatch);
};
const mapStateToProps = state => {
return selectData.getStore(state);
};
return connect(mapStateToProps, mapDispatchToProps)(BaseComponent);
};
export default componentsWrapper;
unit test:
class MockListComponent extends Component {
render() {
return (<div>Fake List</div>);
}
}
Components = componentsWrapper(MockListComponent, selectComponents);
wrapper = shallow(<Components store={store} />).dive();
instance = wrapper.instance()
// Here instance is null hence the rest will fail.
instance.foo = jest.fn();
instance is null because "Connect" component is functional or stateless. Source
NOTE: With React 16 and above, instance() returns null for stateless functional components.
I don't know how to get the instance to not be null or maybe refactor the code. I appreciate any help or hint.
These are the library versions I am trying to use:
react#16.2.9
enzyme#3.3.0
redux#4.0.5

I am not sure what is the exact behaviour you are trying to test, but in many cases, there is no need to explicitly 'test' the entire connected component unless you are planning to do integrated testing or functional testing.
To fix the issue you are facing, I would recommend you to export the child component (take note of export on BaseComponent):
const componentsWrapper = (Components, selectData) => {
export class BaseComponent extends Component {
// ... rest of the logic
}
}
Then, on your test file, we use enzyme's .find() to find the node that matches the selector. In this case, our selector is BaseComponent.
const wrapper = shallow(<Components store={store} />)
const wrapperInstance = wrapper.dive().find(BaseComponent).instance();
From there, you will have access to BaseComponent's class instance, and you can use it to call its methods, or spy on its methods.
The reason why you are facing the error stated on the question is explained on the enzyme documentation:
With React 16 and above, instance() returns null for stateless
functional components.
Therefore, you can only call .instance() on the component itself, rather than the entire connected component.

Related

HoC component connected to redux arround component connected to redux

I have base component, let's say BaseContainer that connects to redux and has some methods. Now I want to create few CustomContainer components that should be connected to redux too and should have access to all methods and state of BaseContainer component.
So BaseContainer would be:
class BaseContainer extends React.Component {
state = {};
method1() {};
method2() {};
method3() {};
}
export default connect(mapStateToProps, mapDispatchToProps)(BaseContainer);
And one of CustomContainers should be:
class CustomContainer extends BaseContainer {
// should have access to all imports, methods and props of BaseContainer
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomContainer);
Tried this but seems that inheritance does not work well in React and it is not recommended too.
Here I get error Super expression must either be null or a function.
Tried other approach with using HoC:
class CustomContainer extends React.Component {
// should have access to all imports, methods and props of BaseContainer
}
export default connect(mapStateToProps, mapDispatchToProps)(BaseContainer(CustomContainer));
and now I'm facing error: Unhandled Rejection (TypeError): Object(...) is not a function
What is wrong and how can I achieve that my CustomContainer has access to all imports, props and state of BaseContainer ?
You should probably read over the react docs, specifically Composition vs. Inheritance. React favors composition over inheritance. BaseContainer also isn't a Higher Order Component, but rather it's a regular component, and it doesn't appear to return anything to render.
Higher Order Component
Here's an implementation I think would help get you close to what you're after
const withBaseCode = WrappedComponent => {
class BaseContainer extends Component {
state = {};
method1 = () => {...}
method2 = () => {...}
method3 = () => {...}
render() {
return (
<WrappedComponent
method1={this.method1}
method2={this.method2}
method3={this.method3}
{...this.props}
/>
);
}
}
const mapStateToProps = state => ({...});
const mapDispatchToProps = {...};
return connect(mapStateToProps, mapDispatchToProps)(BaseContainer);
};
Then to use it's just a normal HOC, so given some component
const CustomContainer = ({ method1, method2, method3, ...props}) => {
...
return (
...
);
};
const CustomContainerWithBaseCode = withBaseCode(CustomContainer);
Some App container
function App() {
return (
<div className="App">
...
<CustomContainerWithBaseCode />
</div>
);
}
Demo of above code minus actually connecting to a redux store.

Apollo Compose and Enzyme mount

I have an issue here. I'm using React Apollo as GraphQL client and compose from Apollo to create my component separated from my GraphQL rules.
Everything works fine but I need to write some tests. So here's my component:
export const NestedComponent(someProperty: MyProperty) => (
<div>
Some simple content...
{ someProperty.value }
Another content here...
</div>
);
class ParentComponent extends Component<Props, {
anotherProperty: string
}> {
constructor(props) {
super(props);
}
render() {
return (<NestedComponent someProperty={ value: anotherProperty } />);
}
}
const withSomeData = graphql(myQuery, {
props: ({ data }) => ({ someProperty: { value: data.value }),
});
export default compose(
withSomeData,
withApollo,
)(withTheme(WorkstreamSelector));
As you can see, it's a very simple component with a nested function component. No secrets. So, here's my problem, I'm using Airbnb Enzyme to test my components. For this, I use mount from Enzyme (this way I can mock Apollo layer and stuff the way I want) and then comes the problem.
When I simply try to mount a component that is out of Apollo's compose function, the mount function returns me the complete DOM of the object, but when I use compose, no matter what I do, I always get only the first level (note that I'm not using shallow, I'm using mount), and I can't test the nested components inside my parent's component.
Can someone help me?

React Native HOC's and duplicate lifecycle method functions

So I just discovered HOC's (Higher Order Functions) yesterday and they are pretty sweet. In my development I do use lifecycle methods like componentDidUpdate fairly frequently. I have found that I would like to use many HOCs for one wrapper component like so:
export default compose(
connect(mapStateToProps),
RefreshHOC(FeedScreen),
LoggedInHOC(FeedScreen)
)(FeedScreen)
I have noticed that if I have the same lifecycle (say componentDidUpdate) method in the WrapperComponent and one of the HOCs both lifecycle methods work. The problem arises when I have a Wrapper Component that has a lifecycle method then two or more HOC's also have the same lifecycle method, then only the first HOC's lifecycle method runs (in the above example componentDidUpdate runs in RefershHOC but not in LoggedInHOC).
Is there a better way to design this pattern? Am I just getting some syntax incorrect? Should I just have 1 HOC for each special lifecycle method that I want to group logic?
Edit
Here is some example code that I think is sufficient enough:
class FeedScreen extends Component {
componentDidUpdate(prevProps) {
let {appbase, auth, dispatch} = this.props
console.log('fire')
}
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default compose(
connect(mapStateToProps),
LoggedInHOC(FeedScreen),
LoggedInHOC2(FeedScreen)
)(FeedScreen)
export const LoggedInHOC = WrapperComponent => props => class
ViewWithPropChanges extends Component {
componentDidUpdate(prevProps) {
console.log('fire LIHOC')
}
render(){
return (<WrapperComponent {...this.props}/>)
}
}
}
export const LoggedInHOC2 = WrapperComponent => props => class ViewWithPropChanges extends Component {
componentDidUpdate(prevProps) {
console.log('fire LIHOC2')
}
render(){
return (<WrapperComponent {...this.props}/>)
}
}
EDIT
Some of your code seems a bit strange to me:
export const LoggedInHOC = WrapperComponent => props => class
// Later
export default compose(
LoggedInHOC(FeedScreen)
)(FeedScreen)
LoggedInHOC here is a function that takes a component and returns a function that returns a component when it should probably be only a function that takes a component and returns a component.
I'm going to assume that the role your LoggedInHOC is to check whether a user is connected somehow, display the wrapped component if that's the case and redirect the user/show a login form otherwise.
You could write it like that:
export const LoggedInHOC = Component => class extends React.Component {
render () {
// Check if the user is connected
if (connected) {
return (
<Component
{...this.props}
/>
);
}
return <p>User not connected</p>;
}
};
And you would wrap your component like that
export default LoggedInHOC(Component);
// Or if you want to chain multiple hocs:
export default compose(
LoggedInHOC,
AnotherHOC
)(Component);
Now back to your original question about chaining multiple HOCs and componentDidUpdate lifecycle. I'm not sure what is the exact problem in your case, but writting:
export default compose(
HOC1,
HOC2
)(Component);
is equivalent to HOC1(HOC2(Component)). So in term of composition you have:
HOC1
HOC2
Component
And you have to keep in mind that when your HOC1 wrapper is updated, that will trigger an update in your HOC2 and in your Component but if you update your HOC2, that will not trigger an update to your HOC1.
I made a example codepen that displays a component wrapped in multiple HOCs each implementing a componentDidUpdate hook

Unable to pass props

I wrote a react site about 6 months ago and had a suite of Jest tests, which all ran fine. I've created a second project based off this one but for some reason when I try and write the same tests on basic component rendering, they fail.
The error I get is
Invariant Violation: Could not find "store" in either the context or
props of "Connect(ControlBar)". Either wrap the root component in a
<Provider>, or explicitly pass "store" as a prop to
"Connect(ControlBar)".
I've done some reading around and there are a few posts on similar topics, which seem to say that TypeScript/Redux aren't playing well together. However in my last project it was exactly the same as above and all the tests run fine. So not sure if it is just that I have pulled in a newer version of something which causes this breaking change, but hoping someone can point out what I'm doing wrong?
My component
interface IControlBarProps {
includeValidated: boolean,
includeValidatedChanged: (includeValidated:boolean) => void,
}
export class ControlBar extends React.Component<IControlBarProps, {}> {
constructor(props: any) {
super(props);
}
public render() { ... }
}
function mapStateToProps(state: IStoreState) {
return {
includeValidated: state.trade.includeValidated
};
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
includeValidatedChanged: (includeValidated:boolean) => {
dispatch(getIncludeValidatedChangedAction(includeValidated))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ControlBar);
My test
import ControlBar from '../ControlBar';
describe('Control Bar Component', () => {
it('should render without throwing an error', () => {
const wrapper = shallow(<ControlBar includeValidated={true} includeValidatedChanged={() => {return;}} />);
expect(wrapper.find('div.Control-bar').exists()).toEqual(true);
})
})
Import your component not connected to Redux, the named export, not the default export of the connected one.
import { ControlBar } from '../ControlBar';
You import ControlBar component wrapped with Redux (export default). To unit test ControlBar try
import { ControlBar } from '../ControlBar';

Nested components testing with Enzyme inside of React & Redux

I have a component SampleComponent that mounts another "connected component" (i.e. container). When I try to test SampleComponent by mounting (since I need the componentDidMount), I get the error:
Invariant Violation: Could not find "store" in either the context or
props of "Connect(ContainerComponent)". Either wrap the root component
in a , or explicitly pass "store" as a prop to
"Connect(ContainerComponent)".
What's the best way of testing this?
Enzyme's mount takes optional parameters. The two that are necessary for what you need are
options.context: (Object [optional]): Context to be passed into the component
options.childContextTypes: (Object [optional]): Merged contextTypes for all children of the wrapper
You would mount SampleComponent with an options object like so:
const store = {
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ... whatever state you need to pass in ... })
}
const options = {
context: { store },
childContextTypes: { store: React.PropTypes.object.isRequired }
}
const _wrapper = mount(<SampleComponent {...defaultProps} />, options)
Now your SampleComponent will pass the context you provided down to the connected component.
What I essentially did was bring in my redux store (and Provider) and wrapped it in a utility component as follows:
export const CustomProvider = ({ children }) => {
return (
<Provider store={store}>
{children}
</Provider>
);
};
then, I mount the SampleComponent and run tests against it:
it('contains <ChildComponent/> Component', () => {
const wrapper = mount(
<CustomProvider>
<SampleComponent {...defaultProps} />
</CustomProvider>
);
expect(wrapper.find(ChildComponent)).to.have.length(1);
});
Option 1)
You can wrap the container component with React-Redux's Provider component within your test. So with this approach, you actually reference the store, pass it to the Provider, and compose your component under test inside. The advantage of this approach is you can actually create a custom store for the test. This approach is useful if you want to test the Redux-related portions of your component.
Option 2)
Maybe you don't care about testing the Redux-related pieces. If you're merely interested in testing the component's rendering and local state-related behaviors, you can simply add a named export for the unconnected plain version of your component. And just to clarify when you add the "export" keyword to your class basically you are saying that now the class could be imported in 2 ways either with curly braces {} or not. example:
export class MyComponent extends React.Component{ render(){ ... }}
...
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
later on your test file:
import MyComponent from 'your-path/MyComponent'; // it needs a store because you use "default export" with connect
import {MyComponent} from 'your-path/MyComponent'; // don't need store because you use "export" on top of your class.
I hope helps anyone out there.
There is also the option to use redux-mock-store.
A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.
The mock store provides the necessary methods on the store object which are required for Redux.
You can specify optional middlewares and your app specific initial state.
import configureStore from 'redux-mock-store'
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {}
const store = mockStore(initialState)
const wrapper = mount(<SampleComponent store={store}/>)
You can use name export to solve this problem:
You should have:
class SampleComponent extends React.Component{
...
render(){
<div></div>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SampleComponent)
You can add a export before class:
export class SampleComponent extends React.Component{
and import this component with no redux store:
import { SampleComponent } from 'your-path/SampleComponent';
With this solution you don't need to import store to your test files.
in an attempt to make the use of decorator syntax more testable I made this:
https://www.npmjs.com/package/babel-plugin-undecorate
input:
#anyOldClassDecorator
export class AnyOldClass {
#anyOldMethodDecorator
method() {
console.log('hello');
}
}
output:
#anyOldClassDecorator
export class AnyOldClass {
#anyOldMethodDecorator
method() {
console.log('hello');
}
}
export class __undecorated__AnyOldClass {
method() {
console.log('hello');
}
}
Hopefully this can provide a solid Option 3!

Resources