I'm using react-testing-library for unit testing React components.
import React from 'react'
import 'react-testing-library/cleanup-after-each'
import {fireEvent, render, cleanup} from 'react-testing-library'
import MyComponent from '../MyComponent';
const translations = require('../../translations');
afterEach(cleanup);
describe('test MyComponent', () => {
it('render functional component', () => {
// extract container which is 'HTMLDivElement'
const {container} = render(<MyComponent translations={translations} />)
})
How do I manipulate the container element as a DOM element? E.g.
console.log(container.classList, container.id, container.innerHTML);
using the props and methods listed at: https://developer.mozilla.org/en-US/docs/Web/API/Element. I can't get any values so far.
Related
Help write a simple integration test for a component in React (the component uses the useContext hook). The test should verify that buttons were pressed and handlers called (it's my code: https://codesandbox.io/s/lingering-violet-n11hu).
The code of the component that validates the test:
import React, {useContext} from "react";
import {StoreContext} from "../../reducer/context";
import moment from "moment";
import Delay from "../delay/delay";
let queue = Promise.resolve();
const Interface = () => {
const {state, dispatch} = useContext(StoreContext);
const handleLogSet = e => {
const timeout = parseInt(e.target.getAttribute("data-delay"), 10);
const timePress = moment().format("LTS");
queue = queue.then(() => Delay(timeout, timePress)).then(res => dispatch({
type: "SET_LOG", payload: "\n" + res
}));
};
const handleReset = () => {
dispatch({type: "RESET"});
};
return (
<div className="block">
<button className="btn" data-delay="1" onClick={handleLogSet}>Кнопка 1</button>
<button className="btn" data-delay="2" onClick={handleLogSet}>Кнопка 2</button>
<button className="btn" data-delay="3" onClick={handleLogSet}>Кнопка 3</button>
<button className="btn" onClick={handleReset}>Reset</button>
<textarea value={state.join("")} readOnly={true}/>
</div>
);
};
export default Interface;
Tried different test options, but none work. I tried, for example, like this:
import {configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Interface from "./interface";
import React, { useContext } from "react";
import { StoreContext } from "../../reducer/context";
configure({ adapter: new Adapter() });
const { state } = useContext(StoreContext);
it(`Click by button calls callback`, () => {
const handleLogSet = jest.fn();
const component = shallow(<Interface
state={state}
/>);
component.find(`.button`).simulate(`click`);
expect(handleLogSet).toHaveBeenCalledTimes(1);
});
Various errors were issued, including the following: "Invalid hook call. Hooks can only be called inside of the body of a function component".
I would be very grateful for an example of a working code and a brief explanation. Thanks a lot, everyone!
So everything was simple enough. It is worth noting that when testing components that use useContext using the shallow method of the Enzyme library, there are recognized difficulties. So far, it has not been possible to solve them directly.
The first thing to do is create a custom hook. You can do it like this:
import React, {useContext} from 'react';
export const useAppContext = () => useContext(AppContext);
const AppContext = React.createContext();
export default AppContext;
This is done in order not to use useContext directly in the component under test.
The e2e test itself looks something like this:
import React from "react";
import {configure, shallow} from "enzyme";
import * as AppContext from "../../reducer/context";
import Adapter from "enzyme-adapter-react-16";
import Interface from "./interface";
configure({adapter: new Adapter()});
it(`Click by Set and Reset buttons calls callback`, () => {
const contextValues = {state: ["Mock state"]};
const handleReset = jest.fn();
const handleLogSet = jest.fn();
jest
.spyOn(AppContext, "useAppContext")
.mockImplementation(() => contextValues);
const wrapper = shallow(
<Interface
onReset={handleReset}
onLogSet={handleLogSet}
/>
);
wrapper.find(`.block__btn--reset`).simulate(`click`);
expect(handleReset).toHaveBeenCalledTimes(1);
wrapper.find(`.block__btn--set`).forEach(item => {
item.simulate(`click`);
expect(handleReset).toHaveBeenCalledTimes(1);
});
});
Thus, we mimic the implementation of our custom code and pass this value to the context object.
I'm testing a component with a nested component inside which use redux. I'm using shallow test for the component.
This is my test:
describe("Header", () =>
void it("renders correctly", () => {
const renderer = new ShallowRenderer()
const tree = renderer.render(<Header />)
expect(tree).toMatchSnapshot();
})
The snapshot output is:
exports[`Header renders correctly 1`] = `
<mockConstructor
render={[Function]}
/>
`;
Is this correct? Shouldn't a snapshot show my component?
Try using shallow from the enzyme package:
import { shallow } from 'enzyme';
import Header from './Header';
describe('Header', () => {
it('should render', () => {
const wrapper = shallow(<Header />);
expect(wrapper).toMatchSnapshot();
});
});
I changed my code as you say and the snapshot output for your snippet is:
exports[`Header renders correctly 1`] = `ShallowWrapper {}`;
Looking information about this output i found Jest/Enzyme ShallowWrapper is empty when creating Snapshot
Basically i have to use enzyme-to-json, so i changed my code to:
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
...
describe("Header", () =>
void it("renders correctly", () => {
const tree = shallow(<Header />)
expect(toJson(tree)).toMatchSnapshot()
})
)
In the github site for enzyme-to-json there is a example that show as my test
import React, {Component} from 'react';
import {shallow} from 'enzyme';
import toJson from 'enzyme-to-json';
it('renders correctly', () => {
const wrapper = shallow(
<MyComponent className="my-component">
<strong>Hello World!</strong>
</MyComponent>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
But the snapshot is:
exports[`Header renders correctly 1`] = `
<mockConstructor
render={[Function]}
/>
`;
So I converted an import used in a class component to React.lazy import api and wrapped it in a Suspense tag. When I test that class component, enzyme throws an error "Enzyme Internal Error: unknown node with tag 13". Is there a way to render and test the mount of the lazy loaded component rather than using shallow render?
I've already tried async await to wait until the promise of the lazy load resolved but that didn't work neither, like so:
it('async await mount', () async () => {
const wrapper = await mount(<Component />)
}
here's example code:
Component.js
import React, { PureComponent, Suspense } from 'react'
const ChildComponent = React.lazy(() => import('../ChildComponent'))
export default class Component extends PureComponent {
constructor() {
super()
this.state = {
example: null
}
}
doSomething = () => this.setState({
example: 'example'
})
render() {
return (
<div>
<p>Example</p>
<Suspense fallback={<div>...loading</div>}>
<ChildComponent
example={this.state.example}
doSomething={this.doSomething}
/>
</Suspense>
</div>
)
}
}
Component.test.js
import React from 'react'
import renderer from 'react-test-renderer'
import { mount } from 'enzyme'
import Component from '../../Component'
describe('Component', () => {
// snapshot renders loading and not children
it('example snapshot of tree', () => {
const tree = renderer.create(<Component />).toJSON()
expect(tree).toMatchSnapshot()
})
it('example mount test', () => {
// this test fails and throws error I state above
const wrapper = mount(<Component />)
wrapper.setState({ example: 'example' })
expect(wrapper.state.example).toBe('example')
})
})
I read that Enzyme does not support React 16.6 Suspense API yet but I wanted to know if there was still a way to test the mounted so I can use things like simulate and find API from Enzyme.
Solution
The GitHub issue linked by ChuckJHardy has been resolved merged and released. You are now able to use the mount API in enzyme as of 1.14.0.
References
https://github.com/airbnb/enzyme/pull/1975
I needed to test my lazy component using Enzyme. Following approach worked for me to test on component loading completion:
const myComponent = React.lazy(() =>
import('#material-ui/icons')
.then(module => ({
default: module.KeyboardArrowRight
})
)
);
Test Code ->
//mock actual component inside suspense
jest.mock("#material-ui/icons", () => {
return {
KeyboardArrowRight: () => "KeyboardArrowRight",
}
});
const lazyComponent = mount(<Suspense fallback={<div>Loading...</div>}>
{<myComponent>}
</Suspense>);
const componentToTestLoaded = await componentToTest.type._result; // to get actual component in suspense
expect(componentToTestLoaded.text())`.toEqual("KeyboardArrowRight");
This is hacky but working well for Enzyme library.
I have a Component in my React App that is connected by react-redux with two props
import PropTypes from "prop-types";
import { createNewPlayer } from "../actions/AllActions";
class HomePage extends React.Component {...}
HomePage.propTypes = {
createNewPlayer: PropTypes.func.isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired
}).isRequired
};
export default connect(null,{ createNewPlayer })(HomePage);
at index.js I have ReactDOM.render(<BrowserRouter><Provider store={store}> ...)
and App.js
const App = () => (<Route path="/" exact component={HomePage} />);export default App;
for snapshot testing the HomePage I using Jest and Enzyme I have written
import React from "react";
import toJSON from "enzyme-to-json";
import { shallow, mount } from "enzyme";
import HomePage from "../components/HomePage";
test("should render the HomePage Component", () => {
const props = { createNewPlayer: jest.fn(), push: jest.fn() };
const wrapper = mount(shallow(<HomePage {...props} />));
expect(toJSON(wrapper)).toMatchSnapshot();
});
where I get the Error
Invariant Violation: Could not find "store" in either the context or props of "Connect(HomePage)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(HomePage)".
how should I snapshot test a connected component with proptype
You should not tested connected component. We want to test unconnected version because we want to be able to provide a set of dynamic props. props are data. in testing world, you should create a /fixture directory inside /tests directory. You should keep your baseline data or dummy data inside that directory and then export it. Or you can create your data inside the test file but it will make mess.
export class HomePage extends React.Component {...}
in testing file
import React from "react";
import toJSON from "enzyme-to-json";
import { shallow, mount } from "enzyme";
import {HomePage} from "../components/HomePage";
import props from "../fixtures/createNewPlayer" //in here you should put your data
test("should render the HomePage Component", () => {
//const props = { createNewPlayer:jest.fn(), push: jest.fn(); IMPORTED ALREADY
const wrapper = mount(shallow(<HomePage {...props} />));
expect(toJSON(wrapper)).toMatchSnapshot();
});
Per your sample test, it doesn't look like you're trying to test the entire integration from App down since you only reference HomePage in your test. As such, you can export the class for testing in addition to the default connect export. As such, your tests will use import { HomePage } from '...'; while your other components will use the default import HomePage from '...';
hi everyone I am testing my react application using jest. While testing a component I found that a test breaks unexpectedly throwing error as
Method “props” is only meant to be run on a single node. 0 found instead.
test file
import React from 'react';
import {shallow} from 'enzyme';
import {AddLibraryItem} from '../../components/AddLibraryItem';
import libraryItems from '../fixtures/libraryItems';
let addLibraryItem, history, wrapper;
beforeEach(() => {
addLibraryItem = jest.fn();
history = {push: jest.fn()};
wrapper = shallow(<AddLibraryItem addLibraryItem={addLibraryItem} history={history}/>);
})
test('should execute on submit button successfully', () => {
console.log(wrapper);
wrapper.find('LibraryItemForm').prop('onSubmit')(libraryItems[0]);
expect(addLibraryItem).toHaveBeenLastCalledWith(libraryItems[0]);
expect(history.push).toHaveBeenLastCalledWith("/");
});
Component
import React from 'react';
import {connect} from 'react-redux';
import LibraryItemForm from './LibraryItemForm';
import {addLibraryItem} from '../actions/libraryA';
export class AddLibraryItem extends React.Component {
onSubmit = (libraryItem) => {
this.props.addLibraryItem(libraryItem);
this.props.history.push('/');
}
render () {
return (
<div>
<LibraryItemForm onSubmit={this.onSubmit} />
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
addLibraryItem: (libraryItem) => dispatch(addLibraryItem(libraryItem))
}
}
const ConnectedAddLibraryItem = connect(undefined, mapDispatchToProps)(AddLibraryItem);
export default ConnectedAddLibraryItem;
The piece of test was earlier working very fine and test of 'LibraryItemForm' is also working fine and also rendering perfectly.
I am not getting what is wrong with it.
Is there any fix of it?
You probably forgot to dive():
wrapper.find(LibraryItemForm).dive().prop('onSubmit')(libraryItems[0]);
Enzyme documentation here.