I've created a Higher Order Component / Composed Component to ensure a user is authenticated before loading the Component. It's very basic, but I'm having some trouble testing it. I want to test the points below, which are similar to the tests I already have elsewhere:
Renders the Component (I normally check by looking for a Component specific className)
Has correct props (in my case authenticated)
Renders the wrapped Component if authenticated and renders null if not
The HOC:
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { makeSelectAuthenticated } from 'containers/App/selectors';
export default function RequireAuth(ComposedComponent) {
class AuthenticatedComponent extends React.Component {
static contextTypes = {
router: React.PropTypes.object,
}
static propTypes = {
authenticated: React.PropTypes.bool,
}
componentWillMount() {
if (!this.props.authenticated) this.context.router.push('/');
}
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) this.context.router.push('/');
}
render() {
return (
<div className="authenticated">
{ this.props.authenticated ? <ComposedComponent {...this.props} /> : null }
</div>
);
}
}
const mapStateToProps = createStructuredSelector({
authenticated: makeSelectAuthenticated(),
});
return connect(mapStateToProps)(AuthenticatedComponent);
}
I'm using enzyme and jest for my tests, but haven't found a way of rendering the HOC successfully during my tests.
Any ideas?
Solution thanks to answer below:
import React from 'react';
import { shallow, mount } from 'enzyme';
import { Provider } from 'react-redux';
import { AuthenticatedComponent } from '../index';
describe('AuthenticatedComponent', () => {
let MockComponent;
beforeEach(() => {
MockComponent = () => <div />;
MockComponent.displayName = 'MockComponent';
});
it('renders its children when authenticated', () => {
const wrapper = shallow(
<AuthenticatedComponent
composedComponent={MockComponent}
authenticated={true}
/>,
{ context: { router: { push: jest.fn() } } }
);
expect(wrapper.find('MockComponent').length).toEqual(1);
});
it('renders null when not authenticated', () => {
const wrapper = shallow(
<AuthenticatedComponent
composedComponent={MockComponent}
authenticated={false}
/>,
{ context: { router: { push: jest.fn() } } }
);
expect(wrapper.find('MockComponent').length).toEqual(0);
});
});
The "tricky" part here is that your HOC returns a connected component, which makes testing harder because you have shallow render two layers (the connected component and the actual component) and you have to mock the redux store.
Instead you could define the AuthenticatedComponent upfront and export it as a named export. Than you can test it independently of connect like you test every other component:
export class AuthenticatedComponent extends React.Component {
static contextTypes = {
router: React.PropTypes.object,
}
static propTypes = {
authenticated: React.PropTypes.bool,
composedComponent: React.PropTypes.any.isRequired,
}
componentWillMount() {
if (!this.props.authenticated) this.context.router.push('/');
}
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) this.context.router.push('/');
}
render() {
const ComposedComponent = this.props.composedComponent;
return (
<div className="authenticated">
{ this.props.authenticated ? <ComposedComponent {...this.props} /> : null }
</div>
);
}
}
export default function RequireAuth(ComposedComponent) {
const mapStateToProps = () => {
const selectIsAuthenticated = makeSelectAuthenticated();
return (state) => ({
authenticated: selectIsAuthenticated(state),
composedComponent: ComposedComponent,
});
};
return connect(mapStateToProps)(AuthenticatedComponent);
}
Example test:
import React from 'react';
import { shallow, mount } from 'enzyme';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import RequireAuth, { AuthenticatedComponent } from '../';
const Component = () => <div />;
Component.displayName = 'CustomComponent';
const mockStore = configureStore([]);
describe.only('HOC', () => {
const RequireAuthComponent = RequireAuth(Component);
const context = { router: { push: jest.fn() } };
const wrapper = mount(
<Provider store={mockStore({})}>
<RequireAuthComponent />
</Provider>,
{
context,
childContextTypes: { router: React.PropTypes.object.isRequired },
}
);
it('should return a component', () => {
expect(wrapper.find('Connect(AuthenticatedComponent)')).toHaveLength(1);
});
it('should pass correct props', () => {
expect(wrapper.find('AuthenticatedComponent').props()).toEqual(
expect.objectContaining({
authenticated: false,
composedComponent: Component,
})
);
});
});
describe('rendering', () => {
describe('is authenticated', () => {
const wrapper = shallow(
<AuthenticatedComponent
composedComponent={Component}
authenticated
/>,
{ context: { router: { push: jest.fn() } } }
);
it('should render the passed component', () => {
expect(wrapper.find('CustomComponent')).toHaveLength(1);
});
});
describe('is not authenticated', () => {
const wrapper = shallow(
<AuthenticatedComponent
composedComponent={Component}
authenticated={false}
/>,
{ context: { router: { push: jest.fn() } } }
);
it('should not render the passed component', () => {
expect(wrapper.find('CustomComponent')).toHaveLength(0);
});
});
});
Related
I am having some issues updating my team's code in order to app the inactivity function. But when trying to do that I getting an Error message on my console
TypeError: Object(...) is not a function
If anyone knows or has an idea of what the problem really is please let me know.
Here is the console
Here is the code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as Actions from '../actions';
import NeedContainer from './NeedContainer';
import ProfilContainer from './ProfilContainer';
import DealContainer from './DealContainer';
import AmountContainer from './AmountContainer';
import DurationContainer from './DurationContainer';
import MonthlyContainer from './MonthlyContainer';
import ContributionContainer from './ContributionContainer';
import FeesContainer from './FeesContainer';
import createActivityDetector from 'activity-detector'
import { useState, useEffect } from 'react';
function useIdle(time) {
const [isIdle, setIsIdle] = useState(false)
useEffect(() => {
const activityDetector = createActivityDetector(time)
activityDetector.on('idle', () => setIsIdle(true))
activityDetector.on('active', () => setIsIdle(true))
return () => activityDetector.stop()
}, [])
return isIdle;
}
class SimulatorContainer extends Component {
render() {
const isIdle = useIdle({timeToIdle:1000})
if (! this.props.ready)
return (<div className="wf-loading">Loading ...</div>);
return (
<div className={this.props.className}>
<NeedContainer />
<ProfilContainer />
<AmountContainer />
<ContributionContainer />
<DurationContainer />
<MonthlyContainer />
<DealContainer />
<FeesContainer />
</div>
);
}
}
const mapStateToProps = state => {
return {
ready:state.simulator.ready,
interest:state.simulator.interest,
}
}
const mapDispatchToProps = dispatch => {
return {
isReady: (ready) => {
dispatch(Actions.isReady(ready));
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(SimulatorContainer)
class useIdle extends Component() {
constructor() {
super(props)
this.state = {
isIdle: false
}
}
componentDidMount() {
this.activityDetector = createActivityDetector(time)
this.activityDetector.on('idle', () => this.setState({isIdle: true}))
this.activityDetector.on('active', () => this.setState({isIdle: true}))
}
componentWillUnmount() {
this.activityDetector.stop()
}
render(){
return this.state.isIdle;
}
}
I wonder why the test is failing when I use it with redux hooks:
The code is working finem but the tests are failing for some reason. I am unable to test if the component is being rendered or not.
Component:
import React, { useEffect, useState } from 'react';
import { fetchAllApis } from '../../../redux/actions/marketplace/marketplaceActions';
import { useDispatch, useSelector, connect } from 'react-redux';
import ApiCard from '../ApiCard/ApiCard';
import Spinner from '../../../components/Extras/Spinner/Spinner';
const ApiSection = ({ apiList, error, loading, categories }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchAllApis({ page, category: categories }));
}, [dispatch, categories]);
const renderApiCards = () => {
return apiList.map((each) => (
<ApiCard key={each.apiId} info={each} data-test="ApiCard" />
));
};
if (loading) {
return <Spinner data-test="Spinner" />;
}
if (error) {
return <h1 data-test="Error">Error while fetching</h1>;
}
return (
<div className="ApiSection" data-test="ApiSection">
<div className="ApiSection__cards">{renderApiCards()}</div>
</div>
);
};
const mapStateToProps = ({ marketplaceApiState }) => {
const { apiList, error, loading } = marketplaceApiState;
return {
error,
loading,
apiList: Object.values(apiList),
};
};
export default connect(mapStateToProps)(ApiSection);
Here is the test for the above component:
Test:
import React from 'react';
import { mount } from 'enzyme';
import ApiListSection from './ApiListSection';
import { findByTestAttr, createTestStore } from '../../../../testUtils';
import { Provider } from 'react-redux';
const setup = (props = {}) => {
let initialState = {
marketPlaceState: {
apiList: {
a: { apiId: 'a', name: 'name', description: 'desc', categories: 'cat'}
},
},
};
const store = createTestStore(initialState);
const wrapper = mount(
<Provider store={store}>
<ApiListSection {...props} />
</Provider>
);
return wrapper;
};
describe('ApiListSection Component', () => {
let component;
beforeEach(() => {
component = setup();
});
// assertions
it('Should render without failing', () => {
const apiSection = findByTestAttr(component, 'ApiSection');
expect(apiSection.length).toBe(1); // <===== FAILING HERE !!!!!
});
});
I would really appreciate the help, thanks in advance
I've been able to set up shallow rendering tests for most of my components. This one is a reusable component with a life cycle method, so I believe I need to use mount. However the test is still failing...
this is the test
import React from 'react';
import { mount } from 'enzyme';
import SingleAdhesive from './single-adhesive';
describe('SingleAdhesive Tests', () => {
it('Renders without creashing', () => {
mount(<SingleAdhesive />)
})
});
this is the component to test
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { getAdhesives } from '../services/adhesives';
class SingleAdhesives extends Component {
constructor(props) {
super(props);
this.state = {
adhesives: [],
selected: "",
redirect: false
}
}
componentDidMount() {
const { params } = this.props.match;
this.setState({
adhesives: getAdhesives(),
selected: params
})
}
render() {
const { adhesives, selected } = this.state;
const glue = adhesives.filter(adhesive => adhesive.name === selected.id)
return (
<div className="container m-4 p-2">
{glue.map(item =>
<div key={item.name}>
<h3>{item.name}</h3>
<div>Type: {item.type}</div>
<div>Color: {item.color}</div>
<div>Packaging: {item.packaging}</div>
<div>Shelf life: {item['shelf life']}</div>
<div>Advantages: {item.advantages}</div>
</div>
)}
</div>
);
}
}
export default SingleAdhesives;
i have a component and in my component i have some child component.
in my parent component i have some function and i want to trigged it from child component. So i make it with redux.
It's my parent component:
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import { bindActionCreators } from "redux";
import { splashStop } from "store/actions/Home/splashStop";
import { connect } from "react-redux";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
};
this.goPage = this.goPage.bind(this);
}
componentDidMount() {
}
goPage = () => {
this.props.history.push("/agencies");
};
render() {
if (this.props.homeSplash.splashStart == true) {
myTime.play();
}
return (
<div>
<ChildComponent />
</div>
);
}
}
const mapStateToProps = state => ({
homeSplash: state.homeSplash
});
function mapDispatchToProps(dispatch) {
return {
splashStop: bindActionCreators(splashStop, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(Home));
it's my child component:
here is in my child component at onClick function i dispatch redux action:
triggerSplash = () => {
this.props.splashStart();
};
my action:
export const START_SPLASH =
"START_SPLASH";
export const splashStart = () => {
return dispatch => {
dispatch({
type: START_SPLASH,
payload: true
});
};
};
and my reducer:
import { START_SPLASH } from "store/actions/Home/splashStart";
let initialState = {
splashStart: false
};
export default (state = initialState, action) => {
switch (action.type) {
case START_SPLASH:
return { ...state, splashStart: action.payload };
default:
return state;
}
};
my reducer, action is working correctly.
here is i wonder why myTime.play(); working always when component mount it's just don't care this control:
if (this.props.homeSplash.splashStart == true) {
myTime.play();
}
i place it to wrong place or what ?
In your redux structure, it seems everything OK. But you should provide your childComponent also to make it more clear.
If you have connected redux action correctly in your child component then try this:
<button ... onClick={() => this.triggerSplash()}>Click</button>
Put arrow function inside onClick. Because, in the component initialization, all component functions are called automatically in the render time.
The app is just supposed to display 'Hello' and when you click on it, switch to 'Goodbye', but it won't render 'Hello'. I've set default state, connected everything, etc. but I can't figure out what I'm missing.
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { connect } from 'react-redux';
import "./styles.css";
const switcheroo = () => {
return {
type: 'SWITCH'
};
};
const switchReducer = (state = {value: 'Hello'}, action) => {
switch (action.type) {
case 'SWITCH':
return { ...state, value: 'Goodbye' };
default:
return state;
}
}
class ClickMachine extends React.Component {
render() {
const { value, switcheroo } = this.props;
return(
<div >
<p onClick={switcheroo}>{value}</p>
</div>
)
}
};
const mapStateToProps = (state) => ({
value: state.value,
});
const mapDispatchToProps = (dispatch) => ({
switcheroo: () => dispatch(switcheroo()),
});
connect(mapStateToProps, mapDispatchToProps)(ClickMachine);
const store = createStore(switchReducer);
class AppWrapper extends React.Component {
render() {
return (
<Provider store={store}>
<ClickMachine />
</Provider>
);
};
};
const rootElement = document.getElementById("root");
render(<AppWrapper />, rootElement);
My CodeSandbox is here: https://codesandbox.io/s/k29r3928z7 and I have the following dependencies:
react
react-dom
react-redux
redux
Its because you've not assigned the connect function to a component, without which redux won't be associated with the ClickMachine Component
just change this line, it will work
ClickMachine = connect(mapStateToProps, mapDispatchToProps)(ClickMachine);
Sandbox link https://codesandbox.io/s/4x2pr03489