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.
Related
I'm getting data via an Axios GET request from a local API and trying to save the data in a Context Object.
The GET request works properly when I run it outside the Context Provider function. But when I put it within a UseEffect function with no dependencies - ie. useEffect( () => /* do something*/, [] )the useEffect hook never fires.
Code here:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [])
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I never see 'this does not' in the console (double and triple checked). I'm trying to initialise the context to an empty value at first, make the GET request on first render, and then update the context value.
I'd really appreciate any help on what I'm doing wrong.
EDIT - Where Context Provider is being rendered
import React from 'react';
import AppNavbar from "./Components/AppNavbar";
import ShoppingList from "./Components/ShoppingList";
import ItemModal from "./Components/ItemModal";
//IMPORTED HERE (I've checked the import directory is correct)
import ItemsContextProvider from "./ItemsContext";
import { Container } from "reactstrap"
import "bootstrap/dist/css/bootstrap.min.css";
import './App.css';
function App() {
return (
<div className="App">
<ItemsContextProvider> //RENDERED HERE
<AppNavbar />
<Container>
<ItemModal />
<ShoppingList /> //CONSUMED HERE
</Container>
</ItemsContextProvider>
</div>
);
}
export default App;
I have it being consumed in another file that has the following snippet:
const {items, dispatch} = useContext(ItemsContext);
console.log(items, dispatch);
I see console logs showing the empty array I initialised outside the useEffect function in the Context Provider and also a reference to the dispatch function.
I had the same problem for quite a while and stumbled upon this thred which did not offer a solution. In my case the data coming from my context did not update after logging in.
I solved it by triggering a rerender after route change by passing in the url as a dependency of the effect. Note that this will always trigger your effect when moving to another page which might or might not be appropriate for your usecase.
In next.js we get access to the pathname by using useRouter. Depending on the framework you use you can adjust your solution. It would look something like this:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
import { useRouter } from "next/router"; // Import the router
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
const router = useRouter(); // using the router
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [router.pathname]) // trigger useEffect on page change
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I hope this helps anyone in the future!
<ItemsContextProvider /> is not being rendered.
Make sure is being consumed and rendered by another jsx parent element.
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.
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 tried many solutions I found on google to test if Component.propTypes was set properly at a react component, but none of them worked for me. Even though I get a console warning when running my React application on the browser if the properties are passed incorrectly, when I run jest I can't capture that warning in any way that I tried. Here is my best attempt:
App.js:
export class App extends Component {
constructor(props) {
super(props);
}
render() {
return <div/>;
}
}
App.propTypes = {
images: PropTypes.array.isRequired
};
function mapStateToProps(state) {
const {images} = state;
return {images: images};
}
export default connect(mapStateToProps)(App);
App.test.js:
import React from 'react';
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
import {shallow} from 'enzyme';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import sinon from 'sinon'
import {Provider} from 'react-redux';
import App from './App';
const expect = chai.use(chaiEnzyme()).expect
const mockStore = configureStore([thunk]);
const wrap = (initialState, props) => {
return shallow(<Provider store={mockStore(initialState)}><App {...props} /></Provider>)
};
describe('App container', () => {
it('validates properties', () => {
const stub = sinon.stub(console, 'warn');
console.warn.reset();
React.createElement(App, {});
expect(stub.calledOnce).to.equal(true);
expect(stub.calledWithMatch(/Failed prop type/)).to.equal(true);
console.warn.restore();
});
it('renders without crashing', () => {
wrap();
});
it('is react-redux connected', () => {
const wrapper = wrap();
expect(wrapper.find('Connect(App)')).to.have.length(1);
});
it('correctly maps properties', () => {
const wrapper = wrap({images: []});
expect(wrapper.props().images).to.equal([]);
});
});
According to what I've read online on various GitHub issue threads, it seems like a common approach is to make console.warn/console.error throw.
So when you are writing your tests you can do something like
expect(// Render Component //).to.throw();
Hope this helps.
More info: https://github.com/airbnb/enzyme/issues/588
I'm writing a React-Native application in which I have a screen I need to test:
MyScreen.js
import React, { Component } from "react";
import CustomTable from "./CustomTable";
export default MyScreen extends Component {
render() {
return <CustomTable />;
}
}
CustomTable.ios.js
import React, { Component } from "react";
import { View } from "react-native";
import TableView from "react-native-tableview";
export default MyScreen extends Component {
render() {
return (
<View>
...some stuff
<TableView />
</View>
);
}
}
react-native-tableview calls some iOS specific code so I have mocked it out by simply returning the android version (CustomTable.android.js) in the __mocks__ folder
__mocks__/CustomTable.ios.js
import CustomAndroidTable from "../CustomTable.android";
export const CustomTable = CustomAndroidTable;
What I want to do is test MyScreen.js with Jest, but I want it to use the __mock__/CustomTable.ios. How do I go about getting it to do that? Is that even possible with Jest? My current test file looks like:
tests/MyScreen.test.js
import React from "react";
import renderer from "react-test-renderer";
import MyScreen from "../src/MyScreen";
describe("test", () => {
it("works", () => {
jest.mock("../src/CustomTable.ios");
const tree = renderer.create(
<MyScreen />,
).toJSON();
expect(tree).toMatchSnapshot();
});
But it still calls the original version of CustomTable.ios. What am i doing wrong?
You should call jest.mock outside of your suite test. It should be immediately after your imports.
import React from "react";
import renderer from "react-test-renderer";
import MyScreen from "../src/MyScreen";
jest.mock("../src/CustomTable.ios");
describe("test", () => {
it("works", () => {
const tree = renderer.create(
<MyScreen />,
).toJSON();
expect(tree).toMatchSnapshot();
});