Unable to set context when rendering child components - javascript

I'm trying to test a custom Material-ui React component with Enzyme but getting the following error:
ERROR: 'Warning: Failed context type: Required context 'muiTheme' was not specified in 'ChildComponent'.
What I've tried is to set a context according to this. The component that I want to reach and test is a child component.
const root = shallow(<RootComponent />, {context: {muiTheme}, childContextTypes: {muiTheme: React.PropTypes.object}})
const child = root.find(ChildComponent)
child.render() // <--- this line is throwing the error
update: this is related

I'm not sure this is the solution but it's one step closer to the goal.
const root = mount(<RootComponent />, {
context: {muiTheme},
childContextTypes: {muiTheme: React.PropTypes.object}
})
const child = root.find(ChildComponent)
Notice, I use mount instead of shallow. The issue is with this I can't use child.find({prop: 'value'}) any longer - return 0 items...

You need to provide the <muiThemeProvider> component.
Here is an example on how to do :
import React from 'react';
import { mount } from 'enzyme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Input from './Input';
describe('<Input />', () => {
const mountWithTheme = (node) => mount(<MuiThemeProvider>{node}</MuiThemeProvider>);
it('calls componentDidMount', () => {
sinon.spy(Input.prototype, 'componentDidMount');
mountWithTheme(<Input />);
expect(Input.prototype.componentDidMount.calledOnce).to.equal(true);
});
});

Related

Global beforeEach and afterEach Jest in React

I'm using react, so before each test I need to create a container element. Currently, I have something like this:
import { screen } from '#testing-library/react';
import { render, unmountComponentAtNode } from 'react-dom';
import Form from './Form';
let container: Element | null = null;
// setup a DOM element as a render target
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
// cleanup on exiting
afterEach(() => {
unmountComponentAtNode(container!);
container?.remove();
container = null;
})
test('renders with a heading', () => {
render(
<Form heading={'Form Heading'} />, container
);
const element = screen.getByText(/form heading/i);
expect(element).toBeInTheDocument();
});
As you probably expected, I need to do the beforeEach and afterEach setup for each test file. Can this be made global(after being global, we still need access the to container variable)? Looking forward to your reply!
While this isn't technically the answer to the question you asked, this is the solution to the underlying problem you have.
You are importing the wrong render:
import { render, unmountComponentAtNode } from 'react-dom';
The render function imported from react-dom is used to actually render the result to the browser, not for testing.
You need to: import {render} from '#testing-library/react'. The testing library does all that container management for you.
If you actually need the container for anything, you can still do:
const {container} = render(/* ... */);
But most of the time that is not necessary.

Setting a mockFunction through context on a class based component with jest and enzyme

I have a very simple class based component. Which looks like the following:
class MyComponent extends React.Component {
onPressButton () {
console.warn('button pressed')
const { contextFunction } = this.context
contextFunction()
}
render () {
return (
<div>
My Component
<button onClick={() => onPressButton()}>Press button</button>
</div>
)
}
}
MyComponent.contextType = SomeContext
That is all fine and well and works as expected. However, I am having trouble adding unit tests with jest and enzyme. My current code looks as follows:
test('should render test Press button', async () => {
const contextFunctionMock = jest.fn()
const wrapper = shallow(<MyComponent {...props} />)
wrapper.instance().context = { contextFunction: contextFunctionMock }
console.log('wrapper.instance()', wrapper.instance())
await wrapper.instance().onPressButton() // this works just fine
expect(contextFunctionMock).toHaveBeenCalled() // this errors, basically because ti complains contextFunction is not a function
})
As you can see above, I console.logged my wrapper.instance() to see what is going on.
Interestingly enough, the context on the root of the instance object is indeed what I expected it to be based on setting the context, which is something like:
context: {
contextFunction: [Function: mockConstructor] {
_isMockFunction: true,
getMockImplementation: [Function (anonymous)],
[...Other mock function properties]
}
...
However, there is a second context, which is in the updater property of the wrapper.instance(), and it is an empty object. Basically looks like the following:
updater: <ref *2> Updater {
_renderer: ReactShallowRenderer {
_context: {},
...
}
Not exactly sure if this is the context being used for my component's unit test, but it is currently just an empty object, which makes me think this may be the one being used for it.
Anyway, how can I properly mock my context functions to run on this particular unit tests? Also, why is this happening but does not happen in others with a similar set of circumstances?
Problem
A fundamental problem with your code above is that there's no way to assert that the context function is successfully/failing to be called. Right now, you're clicking a button, but there isn't any indication on what's happening after the button is clicked (nothing is being changed/updated within the context/component to reflect any sort of UI change). So asserting that a contextual function is called won't be beneficial if there's no result of clicking the button.
In addition to the above, the enzyme-adapter doesn't support context that uses the createContext method.
However, there's a work-around for this limitation! Instead of unit testing the component, you'll want to create an integration test with the context. Instead of asserting that a contextual function was called, you'll make assertions against the result of clicking on the button that changes context and how it affects the component.
Solution
Since the component is tied to what's in context, you'll create an integration test. For example, you'll wrap the component with context in your test and make assertions against the result:
import * as React from "react";
import { mount } from "enzyme";
import Component from "./path/to/Component";
import ContextProvider from "./path/to/ContextProvider";
const wrapper = mount(
<ContextProvider>
<Component />
</ContextProvider>
);
it("updates the UI when the button is clicked", () => {
wrapper.find("button").simulate("click");
expect(wrapper.find(...)).toEqual(...);
})
By doing the above, you can make assertions against contextual updates within the Component. In addition, by using mount, you won't have to dive into the ContextProvider to view the Component markup.
Demo Example
This demo utilizes context to toggle a theme from "light" to "dark" and vice versa. Click on the Tests tab to run the App.test.js integration test.
Code Example
App.js
import * as React from "react";
import { ThemeContext } from "./ThemeProvider";
import "./styles.css";
class App extends React.PureComponent {
render() {
const { theme, toggleTheme } = this.context;
return (
<div className="app">
<h1>Current Theme</h1>
<h2 data-testid="theme" className={`${theme}-text`}>
{theme}
</h2>
<button
className={`${theme}-button button`}
data-testid="change-theme-button"
type="button"
onClick={toggleTheme}
>
Change Theme
</button>
</div>
);
}
}
App.contextType = ThemeContext;
export default App;
ThemeProvider.js
import * as React from "react";
export const ThemeContext = React.createContext();
class ThemeProvider extends React.Component {
state = {
theme: "light"
};
toggleTheme = () => {
this.setState((prevState) => ({
theme: prevState.theme === "light" ? "dark" : "light"
}));
};
render = () => (
<ThemeContext.Provider
value={{ theme: this.state.theme, toggleTheme: this.toggleTheme }}
>
{this.props.children}
</ThemeContext.Provider>
);
}
export default ThemeProvider;
index.js
import * as React from "react";
import ReactDOM from "react-dom";
import ThemeProvider from "./ThemeProvider";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById("root")
);
Test Example
An example of how to test against the demo example above.
withTheme.js (an optional reusable testing factory function to wrap a component with context -- especially useful for when you may want to call wrapper.setProps() on the root to update a component's props)
import * as React from "react";
import { mount } from "enzyme";
import ThemeProvider from "./ThemeProvider";
/**
* Factory function to create a mounted wrapper with context for a React component
*
* #param Component - Component to be mounted
* #param options - Optional options for enzyme's mount function.
* #function createElement - Creates a wrapper around passed in component with incoming props (now we can use wrapper.setProps on root)
* #returns ReactWrapper - a mounted React component with context.
*/
export const withTheme = (Component, options = {}) =>
mount(
React.createElement((props) => (
<ThemeProvider>{React.cloneElement(Component, props)}</ThemeProvider>
)),
options
);
export default withTheme;
App.test.js
import * as React from "react";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import withTheme from "./withTheme";
import App from "./App";
configure({ adapter: new Adapter() });
// wrapping "App" with some context
const wrapper = withTheme(<App />);
/*
THIS "findByTestId" FUNCTION IS OPTIONAL!
I'm using "data-testid" attributes, since they're static properties in
the DOM that are easier to find within a "wrapper".
This is 100% optional, but easier to use when a "className" may be
dynamic -- such as when using css modules that create dynamic class names.
*/
const findByTestId = (id) => wrapper.find(`[data-testid='${id}']`);
describe("App", () => {
it("initially displays a light theme", () => {
expect(findByTestId("theme").text()).toEqual("light");
expect(findByTestId("theme").prop("className")).toEqual("light-text");
expect(findByTestId("change-theme-button").prop("className")).toContain(
"light-button"
);
});
it("clicking on the 'Change Theme' button toggles the theme between 'light' and 'dark'", () => {
// change theme to "dark"
findByTestId("change-theme-button").simulate("click");
expect(findByTestId("theme").text()).toEqual("dark");
expect(findByTestId("theme").prop("className")).toEqual("dark-text");
expect(findByTestId("change-theme-button").prop("className")).toContain(
"dark-button"
);
// change theme to "light"
findByTestId("change-theme-button").simulate("click");
expect(findByTestId("theme").text()).toEqual("light");
});
});
As for today, the new context API is not supported by enzyme, the only solution I found is to use this utility https://www.npmjs.com/package/shallow-with-context
import { configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { withContext } from "shallow-with-context";
import MyComponent from "./MyComponent";
configure({ adapter: new Adapter() });
describe("Context", () => {
it("should render test Press button", async () => {
const contextFunctionMock = jest.fn();
const context = { contextFunction: contextFunctionMock };
const MyComponentWithContext = withContext(MyComponent, context);
const wrapper = shallow(<MyComponentWithContext />, { context });
await wrapper.instance().onPressButton();
expect(contextFunctionMock).toHaveBeenCalled();
});
});
https://codesandbox.io/s/enzyme-context-test-xhfj3?file=/src/MyComponent.test.tsx

Redux Container Unit Testing - Unable to access props for wrapped component

Following the React v6 upgrade, my existing test cases for the component are failing.
Here is my component container code TodoContainer.jsx:
import { connect } from 'react-redux';
import Todo from './Todo';
import { initialLoadExecuted } from '../../actions/LoadActions';
const mapStateToProps = state => ({
isCollapsed: true,
pinnedTiles: false,
});
const mapDispatchToProps = dispatch => ({
dispatchInitialLoadExecuted: (tiles) => {
dispatch(initialLoadExecuted(tiles));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Todo);
Here is my test code TodoContainer.test.jsx:
import React from 'react';
import configureStore from 'redux-mock-store';
import {Provider} from 'react-redux';
import TodoContainer from '../../../../src/todo-component/components/Todo/TodoContainer';
import { initialLoadExecuted } from '../../../../src/todo-component/actions/LoadActions';
const mockStore = configureStore();
let store;
describe('Todo Container', () => {
beforeEach(() => {
store = mockStore({
});
it('maps state props correctly', () => {
const wrapper = shallow(<TodoContainer store={store}/>);
wrapper.prop('dispatchInitialLoadExecuted')('Test String);
// Expect statements
});
});
The error i am getting is :
Invariant Violation: Passing redux store in props has been removed and does not do anything. To use a custom Redux store for specific components, create a custom React context with React.createContext(), and pass the context object to React-Redux's Provider and specific components like: . You may also pass a {context : MyContext} option to connect.
Is there a way to to pass store through provider while accessing the props, the same way?
It appears that react-redux v6.0.0 now supports the new Context API added to React v 16.4.0 (and also requires that verson of react now).
I was able to resolve the issue and keep the mockStore pattern by installing react-redux#5.1.1 and react#16.3.0 (before they introduced the Context API).
Further testing: I can use react#16.7.0 as long as I use react-redux#5.1.1
There's an ongoing discussion on the react-redux github issues tab: https://github.com/reduxjs/react-redux/issues/1161
Not a long term solution as I'd be stuck at this version of React, but it does pass the test and I was able to get my 100% code coverage.

Test child components based on the results from an async function

I have a parent component that renders a list of child components, but it's based on the result I got from an API call, set the state of the parent component, and render the child components using this.state. The problem I have right now is that since setState is an async function, by the time the parent component is mounted using Enzyme, the setState hasn't completed yet and therefore the child components don't exist in the DOM. I just want to know what is the correct way to test the existence of child components in this case?
Any help would be appreciated!
ParentComponent.js
componentDidMount() {
someAsyncAPICall().then(status => {
this.setState({status: status});
});
}
render() {
return (
{this.state.status.map((object, rowIndex) =>
<ChildComponent key={object.id}
...
/>
)}
);
}
Mocha unit test for parent
import React from 'react';
import {expect} from 'chai';
import {mount} from 'enzyme';
import fetchMock from 'fetch-mock';
...
describe('ParentComponent', () => {
const baseURL = "http://localhost:8000";
const status = [{
...
}];
before(() => {
fetchMock.get(`${baseURL}/api`, status);
});
after(() => {
fetchMock.reset();
fetchMock.restore();
});
it('contains the correct number of components', (done) => {
const wrapper = mount(<ParentComponent />);
//This is not working - the length of ChildComponent is always 0
expect(wrapper.find(ChildComponent).length).to.equal(status.length);
});
});

Render child components with Enzymejs tests

I'm trying to test a simple component that take some props (it have no state, or redux connection) with Enzyme, it works for the plain elements like <div /> and so on, but when i try to test if the element rendered by the child component exists, it fails.
I'm trying to use mount but it spit me a lot of errors, i'm new in this so, here is my code:
import React, { Component } from 'react';
import WordCloud from 'react-d3-cloud';
class PredictWordCloud extends Component {
render() {
const fontSizeMapper = word => Math.log2(word.value) * 3.3;
const { size, data, show } = this.props;
if (!show)
return <h3 className='text-muted text-center'>No data</h3>
return (
<section id='predict-word-cloud'>
<div className='text-center'>
<WordCloud
data={data}
fontSizeMapper={fontSizeMapper}
width={size}
height={300} />
</div>
</section>
)
}
}
export default PredictWordCloud;
It's just a wrapper for <WordCloud />, and it just recieves 3 props directly from his parent: <PredictWordCloud data={wordcloud} size={cloudSize} show={wordcloud ? true : false} />, anything else.
The tests is very very simple for now:
import React from 'react';
import { shallow } from 'enzyme';
import PredictWordCloud from '../../components/PredictWordCloud.component';
import cloudData from '../../helpers/cloudData.json';
describe('<PredictWordCloud />', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<PredictWordCloud data={cloudData} size={600} show={true} />)
});
it('Render without problems', () => {
const selector = wrapper.find('#predict-word-cloud');
expect(selector.exists()).toBeTruthy();
});
});
For now it pass but if we change the selector to: const selector = wrapper.find('#predict-word-cloud svg'); where the svg tag is the return of <Wordcloud /> component, the tests fails because the assertion returns false.
I tried to use mount instead of shallow, exactly the same test, but i get a big error fomr react-d3-cloud:
PredictWordCloud Render without problems TypeError: Cannot read property 'getImageData' of null.
This is specially weird because it just happens in the test environment, the UI and all behaviors works perfectly in the browser.
You can find your component directly by Component name.
Then you can use find inside your sub-component as well.
e.g
it('Render without problems', () => {
const selector = wrapper.find('WordCloud').first();
expect(selector.find('svg')).to.have.length(1);
});
or
You can compare generated html structure as well via
it('Render without problems', () => {
const selector = wrapper.find('WordCloud').first();
expect(selector.html()).to.equal('<svg> Just an example </svg>');
});

Categories