I have a component CustomerAccountDetails which is wrapped inside a Provider component.
I am spying on the method 'fetchAccountDetails' inside component CustomerAccountDetails for accountId that I have provided
Following is the code of the component CustomerAccountDetails:
class CustomerAccountDetails extends React.Component {
componentDidMount() {
if(this.props.accountId) {
this.props.fetchAccountDetails(this.props.accountId);
}
}
render() {
return <AccountDetails details={this.props.accountDetails}/>
}
}
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
fetchAccountDetails: accountId => getAccountDetails(accountId)
},
dispatch
);
const mapStateToProps = state => {
return {
accountDetails: state.accountDetails
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomerAccountDetails);
Following is the sample code snippet of enzyme:
it('should fetch account details for the account id provided', () => {
const spyOn = jest.spyOn(
CustomerAccountDetails.prototype,
'fetchAccountDetails'
);
const wrapper = mount(
<Provider store={store}>
<CustomerAccountDetails accountId="1234" />
</Provider>
);
expect(spyOn).toHaveBeenCalled();
});
But when I run this above test case, it is showing the error as "Cannot spyOn on a primitive value; undefined given".
How can I solve this issue ?
You can apply the Inversion of Control principle and pass the function as a prop for the component.
It will be easier to test and will make your component more reusable.
it('should fetch account details for the account id provided', () => {
const fetchAccountDetailsMock = jest.fn();
const wrapper = mount(
<Provider store={store}>
<CustomerAccountDetails
accountId="1234"
fetchAccountDetails={fetchAccountDetailsMock} // now you have control over it
/>
</Provider>
);
expect(fetchAccountDetailsMock).toHaveBeenCalled();
});
Related
I written a custom logic for handling async route loading bundles in react-router-dom .v4. It's work perfectly. But also I heard about useful package with nice API to do the same, like React-Loadable. It has one problem, I cannot get the props/state pushed from Redux on the mount of the component throw this package.
My code is rewritten from the custom style to react-loadable style in two examples below. The last one is react-loadable version, that does not throw state/props.
My personal code:
const asyncComponent = getComponent => {
return class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
const { Component } = this.state
if (!Component) {
getComponent().then(({ default: Component }) => {
const { store } = this.props // CAN GET THE REDUX STORE
AsyncComponent.Component = Component;
this.setState({ Component });
});
}
}
render() {
const { Component } = this.state;
if (Component) {
return <Component {...this.props} />
}
return null;
}
};
};
export default withRouter(asyncComponent(() => import(/* webpackChunkName: "chunk_1" */ './containers/Component')))
The same code, but with React-Loadable:
const Loading = () => {
return <div>Loading...</div>;
}
const asyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "" */ './containers/Component')
.then(state => {
const { store } = this.props // CANNOT GET THE REDUX STORE!!
}),
loading: Loading
})
export default withRouter(asyncComponent)
To get the state from Redux store via Provider you should place your asyncComponent in Stateful Component wrapper, like you do in your custom async logic (1st case).
It because Loadable library returns you asyncComponent like a function, not a constructor, that way he cannot get access to current Redux store. So, the working solution will be the next:
const Loading = () => {
return <div>Loading...</div>;
}
const asyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "" */ './containers/Component')
.then(state => {
const { store } = this.props // YOU WILL GET THE REDUX STORE!!
}),
loading: Loading
})
class asyncComponentWrapper extends Component{ // Component wrapper for asyncComponent
render() {
return <asyncComponent {...this.props} />
}
}
export default withRouter(asyncComponentWrapper)
P.S.
I do not know what you try to do, but in case how to make reducer injection inside the current store (probably it's exactly what you trying to do), you need to include you Redux store explicitly by import, not from the Provider state.
I have such component. It is a wrapper component for another component. There is onClick function, which should call the log if is mouse event
import log from './log';
export function withPressedLog(
Component,
options,
) {
class WithPressedLogComponent extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
public render() {
const { ...props } = this.props;
return <Component {...props} onClick={this.onClick} />;
}
private onClick(e) {
if (this.props.onClick !== undefined) {
this.props.onClick(e);
}
if (e) {
this.props.log();
}
}
}
const mapDispatchToProps = {
log: () => log(options),
};
return connect(
undefined,
mapDispatchToProps,
)(WithPressedLogComponent);
}
I need to test is it called this.props.log. I have a unit test, but it not works. How I can do it using jest, enzyme?
it("should not log if has not mouse event", () => {
const onClickMock = jest.fn();
const logMock = jest.fn();
const ButtonWithLog = withPressedLog(Button, {
type: "BUTTON_PRESSED",
});
const subject = mountProvider(ButtonWithLog, { onClick: onClickMock, log: logMock });
const mockedEvent = { target:{} };
subject.find(ButtonWithLog).simulate("click", mockedEvent);
expect(onClickMock.mock.calls).toHaveLength(1);
expect(logMock.mock.calls).toHaveLength(0); // not works correctly, always return []
});
store
const store = createStore(() => ({}));
const dispatchMock = jest.fn();
store.dispatch = dispatchMock;
mountProvider function
function mountProvider(
Component,
props,
) {
return mount(
<Provider store={store}>
<Component {...props} />
</Provider>,
);
}
I think the problem here is that you are actually testing the connected component, not the unwrapped component.
Try to isolate the components you are testing more. For example, you can use enzyme's shallow wrapper and the dive method on the connected component, to directly get to the unwrapped component.
Specifically, your problem could be that your connected component is getting the log prop from the store (through mapDispatchToProps), but the store is mocked, so it does not work. In your test, you pass some mock function as prop to the component, but the reference gets lost once the components connects.
helpful thread on github
I´m trying to build a ReactJS high order component using ES6 syntax. Here is my try:
export const withContext = Component =>
class AppContextComponent extends React.Component {
render() {
return (
<AppContextLoader>
<AppContext.Consumer>
{context => <Component {...props} context={context} />}
</AppContext.Consumer>
</AppContextLoader>
);
}
};
Here, AppContextLoader gets context from database and provide it to the context, as:
class AppContextLoader extends Component {
state = {
user: null,
};
componentWillMount = () => {
let user = databaseProvider.getUserInfo();
this.setState({
user: user
});
};
render = () => {
return (
<AppContext.Provider value={this.state}>
{this.props.children}
</AppContext.Provider>
);
};
}
export default AppContextLoader;
And usage:
class App extends Component {
static propTypes = {
title: PropTypes.string,
module: PropTypes.string
}
render = () => {
return (
withContext(
<Dashboard
module={this.props.module}
title={this.props.title}
/>
);
export default App;
For some reason my wrapped component (Dashboard) is not getting my context property, just the original ones (title and module).
How to properly write HOC using ES6 syntax?
You are not using the HOC correctly, you need to pass the component and not the component instance. Also invoking the HOC from within render is a bad patten since each render a new component will be returned, you must write
const DashboardWithContext = withContext(Dashboard);
class App extends Component {
render = () => {
return (
<DashboardWithContext
module={"ADMIN"}
title={"MY APP"}
/>
)
}
}
export default App;
Also in withContext HOC since the returned component is a class, you would access props like {...this.props} instead of {...props}, However it makes sense to use a functional component since you aren't actually using the lifecycle methods
export const withContext = Component => (props) => (
<AppContext.Consumer>
{context => <Component {...props} context={context} />}
</AppContext.Consumer>
);
Working Codesandbox
It should be this.props instead:
<Component {...this.props}
This should be working for you:
render() {
const props = this.props;
return (
<AppContext.Consumer>
{context => <Component {...props} context={context} />}
</AppContext.Consumer>
);
}
You have a few problems:
You're not using the Context API properly - the context is created for the use of a Provider to share a value to one or many Consumers - you are creating with the hoc a new Provider and Consumer.
from your example, you don't need to use Context - use an hoc for new use data - withUserData
You should use this.props and not props
In the usage section, you pass to the hoc an element and not a component
You're not getting the props from withContext
Solution
export const withUserData = BaseComponent =>
class AppContextLoader extends Component {
state = {
user: null,
};
componentWillMount = () => {
let user = databaseProvider.getUserInfo();
this.setState({
user: user
});
};
render = () => {
return (
<BaseComponent {...this.props} {...this.state} />
);
};
}
And usage:
class App extends Component {
static propTypes = {
title: PropTypes.string,
module: PropTypes.string
}
render = () => {
return (
<EnhancedDashboard
module={this.props.module}
title={this.props.title}
/>
);
}
const EnhancedDashboard = withUserData(Dashboard)
export default App;
How do I properly dispatch actions in React Lifecycle Methods?
Hello, friends
I have an action called checkSession() inside my very parent component (App.jsx). This action helps me to get user info.
This is how App.jsx looks like:
class App extends Component {
componentWillMount() {
this.props.checkSession();
}
render() {
if (this.props.auth === null) {
return <div>Loading...</div>;
} else {
return (
<BrowserRouter>
<Route path="/cart" component={Cart} />
</BrowserRouter>
);
}
}
}
const mapStateToProps = ({ auth }) => { auth };
export default connect(mapStateToProps, { checkSession })(App);
How do I properly dispatch another action inside the Cart.jsx component.
I try to dispatch the action inside the componentWillReceiveProps methods but it creates an infinite loop.
My Cart.jsx component looks like this:
import { getCart } from "../../actions";
class Cart extends Component {
componentWillReceiveProps(nextProps, nextState) {
if (nextProps.auth) {
this.props.getCart(nextProps.auth.googleId);
} else {
this.props.getCart(null);
}
}
render() {
......some staff
}
}
const mapStateToProps = ({ auth }) => { auth };
export default connect(mapStateToProps, { getCart })(Cart);
I have just tried to dispatch this action inside the ComponentWillMount - but my props are not ready yet so I got an error.
Help me please with any advice, and sorry for my English.
Maybe you have to detect if the props have changed before firing the dispatch:
componentWillReceiveProps(nextProps, nextState) {
const currentId = this.props.auth && this.props.auth.googleId;
const nextId = nextProps.auth && nextProps.auth.googleId;
if (currentId !== nextId) {
this.props.getCart(nextProps.auth.googleId);
}
}
React - Test Utilities Docs
I have a Login component which will display a Notification component if this.state.error is true.
I'm now writing a Jest test to test this.
import React from 'react'
import ReactTestUtils from 'react-dom/test-utils';
import { shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import Login from './Login'
import Notification from '../common/Notification'
describe('<Login /> component', () => {
it('should render', () => {
const loginComponent = shallow(<Login />);
const tree = toJson(loginComponent);
expect(tree).toMatchSnapshot();
});
it('should contains the words "Forgot Password"', () => {
const loginComponent = shallow(<Login />);
expect(loginComponent.contains('Forgot Password')).toBe(true);
});
// This test fails
it('should render the Notification component if state.error is true', () => {
const loginComponent = ReactTestUtils.renderIntoDocument(
<Login />
);
const notificationComponent = ReactTestUtils.renderIntoDocument(
<Notification />
);
loginComponent.setState({
error: true
}, expect(ReactTestUtils.isDOMComponent(notificationComponent)).toBe(true));
});
});
However currently the test is failing, and I'm not sure why
In the last part of my code I've also tried this to no avail
loginComponent.setState({
error: true
}, expect(ReactTestUtils. isElement(notificationComponent)).toBe(true));
https://facebook.github.io/react/docs/test-utils.html
The render() of my Login component
render() {
const usernameError = this.state.username.error;
const error = this.state.error;
const errorMsg = this.state.errorMsg;
return (
<div className="app-bg">
{ error &&
<Notification message={ errorMsg } closeMsg={ this.closeMessage }/>
}
<section id="auth-section">
<header>
<img src="static/imgs/logo.png"/>
<h1>tagline</h1>
</header>
Also tried this method for testing for the Notification component after setting state.error to true
it('should render the Notification component if state.error is true', () => {
const loginComponent = ReactTestUtils.renderIntoDocument(
<Login />
);
const notificationComponent = ReactTestUtils.renderIntoDocument(
<Notification />
);
// loginComponent.setState({
// error: true
// }, expect(ReactTestUtils.isDOMComponent(notificationComponent)).toBe(true));
const checkForNotification = () => {
const login = shallow(<Login />);
expect(login.find(Notification).length).toBe(1);
};
loginComponent.setState({
error: true
}, checkForNotification());
});
But that test also failed.
Also tried const login = mount(<Login />);
Anyone else running into a problem using Jest and the React Test Utilities?
Figured it out! Did not need React Test Utilities
it('should render the Notification component if state.error is true', () => {
const loginComponent = shallow(<Login />);
loginComponent.setState({ error: true });
expect(loginComponent.find(Notification).length).toBe(1);
});
This will set the state of error to true in the Login component, then check if the Login component contains the Notification component.
This should probably be refactored a bit. The Notification component should probably be always rendered in a more global component (like a Page Wrapper or some sort of other container), and it should probably render null unless there's errors within a global reducer. A Login component probably shouldn't maintain the responsibility and business logic regarding notifications.