unit testing material ui components with jest - reactjs

There is a grid component in material ui and I want it to be unit tested using jest in my React application. Please find my code below
For the above code, how we will write unit test? I went through their testing guide https://material-ui.com/guides/testing, but it is not clear.Any ideas/suggestions are really appreciated
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import "./styles.scss";
const styles = theme => ({
root: {
flexGrow: 1
},
paper: {
height: 140,
width: 100
}
});
function Space({ classes }) {
const x = [1, 2, 3];
return (
<div className="center">
<Grid container className={classes.root}>
<Grid item xs={12}>
<Grid container justify="center" spacing={Number(32)}>
{x.map(value => (
<Grid key={value} item>
<Paper className={classes.paper} />
</Grid>
))}
</Grid>
</Grid>
</Grid>
</div>
);
}
I should be able to unit test the above code using jest

People might hate me for saying this. Since this case does not contain any logic I would just export the function, setup props which reflect real data, and make a snapshot of your shallow rendered function.
Whenever it changes, check it before you update it and all is okay.
EDIT: export the normal component without the withStyles HOC and add the classes to your mocked props.
ANOTHER EDIT:
Your test should probably look something like this in this case:
describe('SpaceComponent', () => {
it('it renders the component correctly', () => {
const props = { classes: { root: 'root', paper: 'paper' } };
const component = shallow(<Space {...props} />);
expect(component).toMatchSnapshot();
});
});

Related

React Props not populated

Trying to learn React and following this tutorial:https://youtu.be/GDa8kZLNhJ4?t=3547
There you have a App.js component that makes Travel Advisor API call that populates the data object:
import React, { useState, useEffect } from "react";
import { CssBaseline, Grid } from "#material-ui/core";
import { getPlacesData } from "./api";
import Header from "./components/Header/Header";
import List from "./components/List/List";
import Map from "./components/Map/Map";
import { PlaceSharp } from "#material-ui/icons";
const App = () => {
const [places, setPlaces] = useState([]);
const [coordinates, setCoordinates] = useState({});
const [bounds, setBounds] = useState(null);
useEffect(() => {
getPlacesData().then((data) => {
console.log(data) // data is there
setPlaces(data);
});
}, []);
return (
<>
<CssBaseline />
<Header />
<Grid container spacing={3} style={{ width: "100%" }}>
<Grid item xs={12} md={4}>
<List />
</Grid>
<Grid item xs={12} md={8}>
<Map
setCoordinates={setCoordinates}
setBounds={setBounds}
coordinates={coordinates}
/>
</Grid>
</Grid>
</>
);
};
export default App;
The following props are passed to Map component:
<Map
setCoordinates={setCoordinates}
setBounds={setBounds}
coordinates={coordinates}
/>
In Map component it gets passed to GoogleMapReact component:
import React from 'react'
import GoogleMapReact from 'google-map-react'
import {Paper, Typography, useMediaQuery} from '#material-ui/core'
import LocationOnOutlinedIcon from '#material-ui/icons/LocationOnOutlined'
import Rating from "#material-ui/lab"
import useStyles from './styles'
const Map = ({setCoordinates, setBounds, coordinates}) => {
const classes = useStyles()
const isMobile = useMediaQuery('(min-width: 600px)')
//console.log(coordinates)
//const coordinates= {lat: 0, lng: 0}
return (
<div className={classes.mapContainer}>
<GoogleMapReact
bootstrapURLKeys={{ key: 'xxxxxxxxxxxxxxxxxxxx'}}
defaultCenter ={coordinates}
center = {coordinates}
defaultZoom = {14}
margin = {[50, 50, 50, 50]}
options = {''}
onChange = {(e) => {
console.log(e) // this is empty but it should have data
setCoordinates({lat: e.center.lat, lng: e.center.lng});
}}
onChildClick = {''}
>
</GoogleMapReact>
</div>
)
}
export default Map
For some reason coordinates prop is not populated in onChange as seen in the video.
I double check the code and cannot find what is stopping it from getting the data.
The API call returns a bunch of restaurants like this:
So it is fetching the data. Only props {coordinates} not getting filled.
Can you see where can be the issue?
There are two pieces of state that handle some state. Those are places and coordinates. Once the App component is loaded, it tries to fetch places and update its state, triggering a re rendering. So far, so good.
The Map Component receives as prop the value of coordinates. coordinates never changes in the flow of the snippet that you posted. Maybe you want to fetch some coordinates from another endpoint? Or maybe from the places data, map through it and set a new state?. Same applies for bounds.
What it looks like it is missing is a call to setCoordinates and setBounds with the new values.

React with MUI's useStyles() giving me a hooks rules violation

I'm getting the dreaded React "Invalid Hook Call" error and I'm not running any hooks.
Installed React using CRA
Installed MUI
Built simple styles page:
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'center',
},
layout: {
minHeight: '230px',
},
}));
export default useStyles;
Added styles to component:
import React from 'react';
import useStyles from './styles';
import Grid from '#material-ui/core/Grid';
import Container from '#material-ui/core/Container';
const SimpleComponent = (props) => {
const classes = useStyles();
return (
<React.Fragment>
<Container className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12}>
<div>Page Content</div>;
</Grid>
</Grid>
</Container>
</React.Fragment>
);
};
export default SimpleComponent;
The app returns no errors on npm start, but the page itself gives me the invalid hook call pointing to the line const classes = useStyles();. That's not a hook.
I've tried re npm installing the app, I've tried moving the call all around, no matter what I do I get that same page. Looks like a lot of people have had similar problems with this configuration but no other solutions have addressed my problem.
Hooks Error Page:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See[https://reactjs.org/warnings/invalid-hook-call-warning.html][1] for tips about how to debug and fix this problem.
SimpleComponent
src/Components/PaymentHistory/PaymentHistory.js:7
4 | import Container from '#material-ui/core/Container';
5 |
6 | const SimpleComponent = (props) => {
> 7 | const classes = useStyles();
8 | return (
9 | <React.Fragment>
10 | <Container className={classes.root}>
View compiled
Please make sure all the dependencies(including #material-ui/core) are correctly installed.
Here is a working demo.

React-Testing: Debug, why this jest snapshot test is failing for a component using Material UI's Grid

I have a reusable component using Material UI Grid containers. I have a simple snapshot test in jest and am receiving the message
Summary of all failing tests
FAIL src/components/Form/__tests__/FormContentGrid.node.js
● renders correctly
Warning: Failed prop type: The prop `xs` of `Grid` must be used on `item`.
in WithStyles(ForwardRef(Grid))
11 | </LabelWrapper>
12 | </Grid>
> 13 | <Grid container spacing={1} xs={10}>
| ^
14 | {children}
15 | </Grid>
16 | </Grid>
Here is the component the test is written for:
import React from 'react';
import PropTypes from 'prop-types';
import { Grid } from '#material-ui/core';
import { LabelWrapper, SideLabel } from './components';
const FormContentGrid = ({ label, children }) => (
<Grid container spacing={1}>
<Grid item xs={1}>
<LabelWrapper>
<SideLabel>{label}</SideLabel>
</LabelWrapper>
</Grid>
<Grid container spacing={1} xs={10}>
{children}
</Grid>
</Grid>
);
FormContentGrid.propTypes = {
label: PropTypes.string,
children: PropTypes.node,
};
export default FormContentGrid;
Here is the test:
import React from 'react';
import { shallow } from 'enzyme';
import FormContentGrid from '../FormContentGrid';
it('renders correctly', () => {
const component = shallow(<FormContentGrid label="test" />);
expect(component).toMatchSnapshot();
});
I'm trying to figure out why the test is failing and how to fix it
It looks like the way to get around this is to set item equal to true in the container.
<Grid container spacing={1} xs={10} item={true}>

Invalid hook call. Hooks can only be called inside of the body of a function component when apply style to class base component with material-ui

I am just started to learn reactjs using material-ui but getting this error when apply style to my component. My code:
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
class NavMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
}
render() {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="Menu"
>
<MenuIcon />
</IconButton>
<Typography
variant="h6"
className={classes.title}
>
News
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</div>
);
}
}
export default NavMenu;
and this is Error:
material-ui makeStyles function only works inside function components, as it uses the new React Hooks APIs inside.
You have two options:
Convert your class component to a functional component.
Use a Higher Order Component as in material-ui docs
I personally recommend the first approach, as this is becoming the new standard in React development.
This tutorial may help you get started with functional components
and check the docs for React Hooks
Use withStyles:
App.js:
import {withStyles} from '#material-ui/core/styles'
// ...
const styles = theme => ({
paper: {
padding: theme.spacing(2),
// ...
},
// ...
})
class App extends React.Component {
render() {
const {classes} = this.props
// ...
}
}
export default withStyles(styles)(App)
Root.js:
import React, {Component} from 'react'
import App from './App'
import {ThemeProvider} from '#material-ui/styles'
import theme from '../theme'
export default class Root extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<App/>
</ThemeProvider>
)
}
}
theme.js:
import {createMuiTheme} from '#material-ui/core/styles'
const theme = createMuiTheme({
palette: {
primary: ...
secondary: ...
},
// ...
}
export default theme
See Theming - Material-UI.
See Higher-order component API.
if you have created a functional component and still run into this issue... the next thing to look for are the dependency versions.
I tried a new stackblitz project to test a material-ui component and got this error. My dependencies were:
react 16.12
react-dom 16.12
#material-ui/core 4.9.14
So I had to change to latest react version using react#latest and react-dom#latest which got me to the following:
react 16.13.1
react-dom 16.13.1
#material-ui/core 4.9.14
Sharing here so that it can help other people who run into this... thanks to this post for the hint

ShallowWrapper is empty when running test

I'm new to testing so I'm trying to add Enzyme to one of my projects. My problem is that when using find(), the ShallowWrapper is empty. Also I'm using Material UI, so I don't know if this is part of the problem.
The component I'm testing
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
const styles = theme => ({
root: {
flexGrow: 1
},
background: {
backgroundColor: "#2E2E38"
},
title: {
color: "#FFE600",
flexGrow: 1
}
});
const NavBar = ({ classes }) => {
return (
<div className={classes.root} data-test="nav-bar">
<AppBar className={classes.background}>
<Toolbar>
<Typography variant="h5" className={classes.title}>
App
</Typography>
</Toolbar>
</AppBar>
</div>
);
};
export default withStyles(styles)(NavBar);
The test
import React from "react";
import { shallow } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
let component = shallow(<NavBar />);
let navbar = component.find("data-test", "nav-bar");
console.log("Log is", component);
expect(navbar).toBe(1);
});
});
Try changing your selector in find(selector) to the following to target the element with data-test="nav-bar". You may need to use dive() to be able to access the inner components of the style component:
import React from "react";
import { shallow } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
const component = shallow(<NavBar />);
// Use dive() to access inner components
const navbar = component.dive().find('[data-test="nav-bar"]');
// Test that we found a single element by targeting length property
expect(navbar.length).toBe(1);
});
});
You can also use an object syntax if you prefer:
const navbar = component.find({'data-test': 'nav-bar'});
Alternatively to using dive(), you could instead mount() the component instead of shallow(), but it depends on your use case:
import React from "react";
import { mount } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
const component = mount(<NavBar />);
// Use dive() to access inner components
const navbar = component.find('[data-test="nav-bar"]');
// Test that we found a single element by targeting length property
expect(navbar.length).toBe(1);
});
});
Hopefully that helps!
I ran into this issue for a different reason where I could not find a SingleDatePicker element. The example in 2. A React Component Constructor from the documentation fixed it for me.
https://enzymejs.github.io/enzyme/docs/api/selector.html#1-a-valid-css-selector
using
wrapper.find(SingleDatePicker).prop('onDateChange')(now);
instead of
wrapper.find('SingleDatePicker').prop('onDateChange')(now);
did the trick for me.

Resources