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.
Related
Note that this is for MUI v5 #mui/material and NOT using v4 #material-ui/core
After finally figuring out how to make #mui/material styles show when using an entry point to emotion to insert scoped shadow DOM styles (see this post How to create insertion point to mount styles in shadow dom for MUI material v5 in React custom element), it turns out the Select drop down is not getting styled correctly for the Demo component which contains the #mui/material components.
Here is the stackblitz https://stackblitz.com/edit/react-d8xtdu-s2cufr?file=demo.js
import React from 'react';
import Demo from './demo';
import { ThemeProvider, createTheme } from '#mui/material/styles';
import { StylesProvider, jssPreset } from '#mui/styles';
import { CacheProvider } from '#emotion/react';
import createCache from '#emotion/cache';
import { create } from 'jss';
import { render } from 'react-dom';
const theme = createTheme();
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const emotionRoot = document.createElement('style');
const mountPoint = document.createElement('div');
shadowRoot.appendChild(emotionRoot);
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
const cache = createCache({
key: 'css',
prepend: true,
container: emotionRoot,
});
render(
<StylesProvider jss={jss}>
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
<Demo />
</ThemeProvider>
</CacheProvider>
</StylesProvider>,
mountPoint
);
}
}
if (!customElements.get('my-element')) {
customElements.define('my-element', MyWebComponent);
}
Instead of showing like this (in addition click events are not getting captured correctly, particularly cannot click on select box arrow to close it):
Dropdown options are showing like this:
You should add MenuProps.disablePortal = true to mount Menu inside shadow DOM (to be able to use scoped styles)
<Select MenuProps={{ disablePortal: true }}>
How to cover the lazy load component in react testing library.
import React, {lazy} from 'react';
const ownerInfo = lazy(() => import('../abc'))
const compone = () => {
return <Suspense><abc /></Suspense>
}
export default compone
test.spec.js
import React from 'react'
import {render, fireEvent} from '#testing-library/react'
import configureStore from 'redux-mock-store'
...
After watching the video, I am able to figure out how to cover the lazy load. Let assume that you have lazyload component.
LazyLoad.jsx:
import React, {lazy} from 'react'
const LazyComponent = lazy(() => import('./LazyComponent'))
const LazyLoad = () => {
return (
<div>
<div> Lazy component is here: </div>
<React.Suspense fallback={null}>
<LazyComponent />
</React.Suspense>
</div>
)
}
export default LazyLoad
LazyComponent.jsx:
import React from 'react'
export default () => <div>I am lazy ! </div>
LazyLoad.spec.jsx:
import React from 'react'
import {render, waitFor } from 'react-testing-library'
import LazyLoad from 'LazyLoad'
test('renders lazy component', async () => {
const { getByText } = render(<LazyLoad />)
await waitFor(() => expect(getByText('I am lazy !' )).toBeInTheDocument())
})
According to Kent Dodds, the React Testing Library creator, you should prefer using findByText rather than waitFor + expect.
Those two bits of code are basically equivalent (find* queries use waitFor under the hood), but the second is simpler and the error message you get will be better.
With that in mind, I suggest to refactor your test like this
import React from 'react'
import {render, waitFor } from 'react-testing-library'
import LazyLoad from 'LazyLoad'
test('renders lazy component', async () => {
const { getByText } = render(<LazyLoad />)
expect(
await screen.findByText('I am lazy !')
).toBeInTheDocument();
})
I have a toggle button that shows and hides a div in my component. Rather new to testing and not sure how to test the toggle. Essentially when studentDisplay is false I have a style that sets the display of a div to none. When studentDisplay is true the style is set to display block. Any suggestions and or ideas would be greatly appreciated.
Method in component I would like to test
import React, {useState} from 'react';
export default function App() {
const [studentDisplay, setStudentDisplay] = useState(false);
function handleStudentDisplay() {
setStudentDisplay(!studentDisplay);
}
return (
<div className="App">
<button onClick={handleStudentDisplay}>
<span>Student Name</span>
</button>
<div style={studentDisplay ? {display:'block'} : {display:'none'}}>
Student
</div>
</div>
);
}
Test example
import React from 'react';
import renderer from 'react-test-renderer';
import { render, fireEvent, act } from '#testing-library/react';
describe('Student Toggle', () => {
it('should display student', () => {
const eventHandler = jest.fn();
const { getByRole } = render(<button onClick={eventHandler}/>);
act(() => {
const button = getByRole('button');
fireEvent.click(button);
});
expect(eventHandler).toHaveBeenCalled();
//how do I test? <div style={studentDisplay ? {display:'block'} : {display:'none'}}>
});
});
You can use expect from RTL. Here is an example to test the display property of an element, you can add a className or fetch using id or element,
const TargetElement = document.getElementsByClassName('button-class-name');
const style = window.getComputedStyle(TargetElement[0]);
expect(style.display).toBe('none');
expect(style.display).toBe('block');
For more information on testing style of react elements, maybe even using styled components, here is a very informative article by Ilya Zykin.
I need to test Button component
it's Button :
import React from "react";
import './Button.css'
const Button = props => {
return(
<button className={"Button"}
onClick={props.onClick}
disabled={props.disabled}
>
{props.children}
</button>
)
}
export default Button
It's my Button.test.js:
import React from 'react';
import {shallow} from 'enzyme';
import Button from "./Button";
it('has a title class', () => {
const wrapper = shallow(<Button/>);
expect(wrapper.hasClass('Button')).to.equal(true);
I'm add enzyme to react. In the console I has an error:
enter image description here
tell me how to solve the problem, i'm new in React.
You need to call hasClass on the button element instead of the wrapper:
expect(wrapper.find('button').hasClass('Button')).to.equal(true);
I am using react along with redux and material-ui to make a component. I am attempting to write an export statement export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
However, this doesn't seem to work I get an error that says
TypeError: Cannot set property 'props' of undefined
this.props = props;
This error is referencing one of my node_modules.
Here is my full code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {withRouter} from 'react-router-dom'
import { withStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
const styles = theme =>({
root: {
maxWidth: 345,
},
})
class FirstPage extends Component {
state = {
feeling: ''
}
//This function will dispatch the users response to index.js
//The dispatch type here is 'SET_FEELING'
submitData=(event) => {
event.preventDefault();
this.props.dispatch({type: 'SET_FEELING', payload: this.state})
this.changeLocation();
}
//This function will update the local state with the users response
handleChange= (event) => {
this.setState({
feeling: event.target.value
})
}
//This function will change the current url when a button is clicked
changeLocation= ()=> {
this.props.history.push('/secondPage')
}
render(){
const { classes } = this.props;
return(
<div>
<Card >
<CardContent className={classes.root}>
<form>
<input onChange={this.handleChange} placeholder='How are you feeling' value={this.state.feeling} />
</form>
</CardContent>
<CardActions>
<Button onClick={this.submitData}>Submit</Button>
</CardActions>
</Card>
</div>
)
}
}
//this export connects the component to the reduxStore as well as allowing us to use the history props
export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
I believe the following code should work:
export default withRouter(connect()(withStyles(styles)(FirstPage)))
Instead of
export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
First of all, connect() returns a function that only accepts an argument. Second, connect() should be wrapped inside withRouter(). This problem is stated in the github docs of React Router.
without using react-redux :
export default (withStyles(styles), withRouter)(FirstPage);