Prevent enzyme shallow rendering on private component - reactjs

How do I prevent shallow rendering on private component with enzyme?
Here is a component example:
// foo.jsx
import React from 'react';
// Private component
const FooSubtitle = ({subtitle}) => {
if (!subtitle) return null;
return <div className="foo__subtitle">{subtitle}</div>;
};
// Public component
const Foo = ({title, subtitle}) => (
<div className="foo">
<div className="foo__title">{title}</div>
<FooSubtitle subtitle={subtitle} />
</div>
);
export default Foo;
Here is my specification:
// foo.spec.js
import React from 'react';
import {shallow} from 'enzyme';
import Foo from './foo.jsx';
describe('Foo', () => {
it('should render a subtitle', () => {
const wrapper = shallow(<Foo title="my title" subtitle="my subtitle" />);
// This test doesn't work, so I cannot test the render of my component
expect(wrapper.find('.foo__subtitle').length).toBe(1);
// This one works, but it is not relevant
expect(wrapper.find('FooSubtitle').length).toBe(1);
});
});
Any idea?
Thanks a lot.

Shallow rendering is useful to constrain yourself to testing a
component as a unit, and to ensure that your tests aren't indirectly
asserting on behavior of child components.
I think you are trying to do what shallow tries to avoid ^^.
You can unit test the private component directly or use render :
expect(wrapper.find(Foo).render().find('.foo__subtitle')).to.have.length(1);
as explaned here : https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/render.md
But in both cases you'll need to export your Component and I must admit I had an error testing it with your component. :/

In this case (and in generally) your private component is just a function, use it as a function in the render of your public component and you will be able to test its render with the shallow wrapper.
<div className="foo">
<div className="foo__title">{title}</div>
{FooSubtitle({subtitle})}
</div>
Otherwise, I'm not sure it's a good idea to have complex private components...

You have to export your private component,
export const FooSubtitle = ...
Now, you are able to test it apart with all its prop variants.
Then you can test the presence of FooSubtitle, with particular props, in the render of Foo component as usual and nothing more.

If you have private components, and you want to test their implementation, you should:
Have at least enzyme v2.5
Look at the Enzyme .dive() API: Shallow render a non-DOM child of the current wrapper
Here is a working example:
// foo.jsx
import React from 'react';
// Private component
const FooSubtitle = ({subtitle}) => {
if (!subtitle) return null;
return <div className="foo__subtitle">{subtitle}</div>;
};
// Public component
const Foo = ({title, subtitle}) => (
<div className="foo">
<div className="foo__title">{title}</div>
<FooSubtitle subtitle={subtitle} />
</div>
);
export default Foo;
// foo.spec.js
import React from 'react';
import {shallow} from 'enzyme';
import Foo from './foo.jsx';
describe('Foo', () => {
it('should render a subtitle', () => {
const wrapper = shallow(<Foo title="my title" subtitle="my subtitle" />);
// This test works, but it is not relevant
expect(wrapper.find('FooSubtitle').length).toBe(1);
// This one need the `dive()` API to work
expect(wrapper.find('FooSubtitle').dive().find('.foo__subtitle').length).toBe(1);
});
});

Related

How do I test that a child component is rendered?

In enzyme you can check for the existence of child component like this:
expect(wrapper.find(ChildComponent)).toHaveLength(1)
What is the equivalent to this test in react testing library? All the online examples I find just cover very simple tests looking for dom elements - none include examples of that render child components. How do you find a child component?
You shouldn't check if your child component is rendered or not, because it's testing implementation details (which testing library doesn't encourage you to do).
You can check some text from your child component is rendered or you can give data-testid to your wrapper element in child and then use .toBeInTheDocument from #testing-library/jest-dom
expect(getByText(/some text/i)).toBeInTheDocument();
or
expect(getByTestId('your-test-id')).toBeInTheDocument();
Updated: Example
// Child Component
function ChildComponent() {
return <div>Child Element</div>;
};
// Parent
export default function Parent() {
return (
<div className="App">
<ChildComponent />
</div>
);
}
Test:
import { render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import Parent from "./App";
test("test child component", () => {
const { getByText } = render(<Parent />);
expect(getByText(/child element/i)).toBeInTheDocument();
});
You can use #testing-library/jest-dom library.
Component:
<div role="root">
<div data-testid="parent">
<div data-testid="child">
content
</div>
</div>
</div>
Test:
import '#testing-library/jest-dom'
import {render} from '#testing-library/react';
describe('My component', () => {
test('should render component2', () => {
const { getByRole, getByTestId } = render(<Component/>);
const root = getByRole('root');
const parent = getByTestId('parent');
const child = getByTestId('child');
expect(root).toContainElement(parent);
expect(parent).toContainElement(child);
expect(child).not.toContainElement(parent); // Pass
expect(child).toContainElement(parent); // Throw error!
});
});
Another solution is to use within function from #testing-library/react library:
import { within } from '#testing-library/react';
...
expect(within(parent).queryByTestId('child')).not.toBeNull();
I used React Test Renderer for that purpose:
import TestRenderer from 'react-test-renderer';
import ItemList from 'components/ItemList';
import LoadingIndicator from 'components/LoadingIndicator';
test('renders loading indication', () => {
const testRenderer = TestRenderer.create(
<ItemList items={[]} isLoading={true}/>
);
const testInstance = testRenderer.root;
testInstance.findByType(LoadingIndicator);
});
I don't think that it's "testing implementation details", quite the opposite - LoadingIndicator component can be modified without need to fix the test case.
since query* return null if element is not found you may just
expect(queryByTestId('test-id-you-provided')).toEqual(null);
expect(queryByTestId('some-other-test-id-you-provided')).not.toEqual(null);
Also getBy* throws if element is not find. So next one should work as well
getByTestId('test-id-you-provided');
I agree with everyone who said, that checking for either text or a test-id inside a child component is testing implementation details.
But we could use mocking, to get rid of the implementation details of the child component.
The code to test:
import { ChildComponent } from 'child-from-somewhere';
export function Parent() {
return (
<div className="App">
<ChildComponent />
</div>
);
}
The test code that checks, if ChildComponent was rendered:
import { render } from "#testing-library/react";
import React from "react";
import { Parent } from "./parent";
jest.mock("child-from-somewhere", () => ({
ChildComponent: () => <div data-testid="ChildComponent">ChildComponent</div>,
}));
describe("ChildComponent component", () => {
it("should be in the document", () => {
const { getByTestId } = render(<Parent />);
expect(getByTestId("ChildComponent")).toBeInTheDocument();
});
});
That way, the test stays independent of changes that are made inside of ChildComponent.

Redux/Enzyme testing of nested components

I have a redux connected navigation bar component that renders two sub-components. One of these is a glorified button (not redux), the other a redux connected search bar.
In a test, I want to be able to render the button and confirm the right behaviour happens when it is clicked. However if I use shallow() then it only renders a placeholder for the button and the button itself is not available to be found and clicked. If I use mount(), then the test fails as my unit test is using the non redux export for the navigation bar, which then tries to render the child search bar component, which is redux connected - and it does not have the store to pass down.
using the non-redux export is fine for testing shallow renders, but what can I do to be able to test my navigation bar component by clicking the button - that can only be fully rendered with a mount() call?
The problem I have is in the below test, if I use shallow() then it cant find the button to simulate the click, as it has only rendered as a placeholder. If I use mount() then it fails to render the <Searchbar /> component, as that is a redux connected component and my test is passing props manually without a connected store.
Is there a way to configure my navigation bar component to pass the props through to the search bar if the store doesn't exist? Or to only conditionally deep render certain components? I only want to render the PanelTileButton, not the SearchBar
My navigation bar component
interface IControlBarProps {
includeValidated: boolean,
includeValidatedChanged: (includeValidated:boolean) => void,
}
export class ControlBar extends React.Component<IControlBarProps, {}> {
constructor(props: any) {
super(props);
}
public render() {
return <div className="Control-bar">
<div className="Control-left" >
<SearchBar />
</div>
<div className="Control-center" />
<div className="Control-right">
{this.getDashboardButton("IV", "Include Validated", this.props.includeValidated, () => this.props.includeValidatedChanged(!this.props.includeValidated))}
</div>
</div>
}
private getDashboardButton(key: string, title: string, newValue: boolean, action: (value:boolean) => void)
{
return <div className="Control-Bar-Right" key={key}>
<PanelTileButton text={title} iswide={false} highlighted={newValue}
// tslint:disable
onClick={() => action(newValue)} />
</div>
}
}
function mapStateToProps(state: IStoreState) {
return {
includeValidated: state.trade.includeValidated
};
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
includeValidatedChanged: (includeValidated:boolean) => {
dispatch(getIncludeValidatedChangedAction(includeValidated))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ControlBar);
My test
it('should handle clicking include validated button', () => {
const mockCallback = jest.fn();
const wrapper = mount(<ControlBar includeValidated={false} includeValidatedChanged={mockCallback} />);
expect(wrapper.find('div.Control-bar').exists()).toEqual(true);
expect(wrapper.find({highlighted: false}).exists()).toEqual(true);
const pb = wrapper.find("PanelTileButton").first();
pb.find('button').simulate('click', {preventDefault() {} });
expect(mockCallback.mock.calls.length).toBe(1);
})
You can also mock components so that you can avoid having to mock the redux store all the time when you prefer to use shallow
In the same folder as SearchBar.tsx, create a sub-folder named __mocks__, and in it place a file with the same name SearchBar.tsx (it's convention) that returns minimal html
import * as React from 'react';
export default function SearchBar() {
return <div>SearchBar</div>
}
And then in your test file
jest.mock('../SearchBar') // this detects the corresponding mock automatically
import { ControlBar } from '../ControlBar';
import { shallow } from 'enzyme';
...
it('should handle clicking include validated button', () => {
const mockCallback = jest.fn();
const wrapper = shallow(<ControlBar includeValidated={false} includeValidatedChanged={mockCallback} />);
...
})
...
For others looking into how to do this, I eventually solved it by wrapping my mounted component in a <Provider> tag and using redux-mock-store to pass a mock store down to the child components that would at least let them render.
import configureMockStore from 'redux-mock-store'
it('should handle clicking include validated button', () => {
const mockCallback = jest.fn();
const mockStore = configureMockStore([])(getTestStore());
const wrapper = mount(<Provider store={mockStore}><ControlBar includeValidated={false} includeValidatedChanged={mockCallback} /></Provider>);
expect(wrapper.find('div.Control-bar').exists()).toEqual(true);
expect(wrapper.find({highlighted: false}).exists()).toEqual(true);
const pb = wrapper.find("PanelTileButton").first();
pb.find('button').simulate('click', {preventDefault() {} });
expect(mockCallback.mock.calls.length).toBe(1);
})

Testing react components that have JSS injected styles with enzyme

I'm having a react component. Let's say Todo
import React, { Component } from 'react';
import injectSheet from 'react-jss';
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export default injectSheet(Todo);
I want to test it with enzyme. And there are two problems with it.
1. Access to the state
(and other component specific features)
When I shallow or mount that composer in the suite I can't get access to its state of course because it's not my component anymore but something new around it.
E.g. this code will give me an error:
it('should have state updated on handleAddTodo', () => {
const todo = shallow(<Todo />);
const length = todo.state('todos').length;
});
It says of course TypeError: Cannot read property 'length' of undefined because the state is not what I expect but this: { theme: {}, dynamicSheet: undefined }
This won't also give me access to props, refs etc.
2. Problems with theme provider
To provide some default colouring to the project like this:
import React, { Component } from 'react';
import Colors from './whatever/Colors';
class App extends Component {
render() {
return (
<ThemeProvider theme={Colors}>
<WhateverInside />
</ThemeProvider>
);
}
}
And of course when running tests it gives me an error [undefined] Please use ThemeProvider to be able to use WithTheme.
So my question is the following. Is there a way to solve this problem in “one single place”. How can I make enzyme agnostic of what is my component wrapped with?
If not, then how do I solve the problem if passing the ThemeProvider features down to the component that I'm testing?
And how can I access the state, ref, props and other things of the wrapped component?
Thank you!
here's what I'd do to test the component,
import React, { Component } from 'react';
import injectSheet from 'react-jss';
const styles = {};
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export { styles, Todo as TodoCmp }
export default injectSheet(styles)(Todo);
and in the test file, I'd add the following:
import { theme } from 'your-theme-source';
const mockReducer = (prev, curr) => Object.assign({}, prev, { [curr]: curr });
const coerceStyles = styles =>
typeof styles === 'function' ? styles(theme) : styles;
const mockClasses = styles =>
Object.keys(coerceStyles(styles)).reduce(mockReducer, {});
import {TodoCmp, styles} from 'your-js-file';
// then test as you'd normally.
it('should blah blah', () => {
const classes = mockClasses(styles);
const todo = shallow(<Todo classes={classes} />);
const length = todo.state('todos').length;
})
Please read more about it in the nordnet-ui-kit library specifically in the test directory. Here's a quick example
It is not related to JSS specifically. Any HOC wraps your component. Ideally you don't test any internals of a component directly.
Components public api is props, use them to render your component with a specific state and verify the rendered output with shallow renderer.
For some edge cases if first and preferred way is impossible, you can access the inner component directly and access whatever you need directly. You will have to mock the props the HOC would pass otherwise for you.
const StyledComponent = injectSheet(styles)(InnerComponent)
console.log(StyledComponent.InnerComponent)
If your component relies on theming, you have to provide a theme provider, always.

Shallow rendering using enzyme simple test not working

I am very new to the enzyme/shallow render testing. I probably don't fully understand it yet.
Using this simplified component:
export const PlacementOption = (props) => <div/>
const UpdatingSelectField = (props) => <div/>
export class DevicePlatforms extends Component{
render(){
return <div>
<UpdatingSelectField/>
{this.props.value.device_platforms && this.props.children}
</div>
}
}
I am trying to tests DevicePlatforms. If this.props.value.device_platforms is not present children are not rendered and if it is they are rendered.
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import { DevicePlatforms } from './Placement.js';
import { PlacementOption } from './Placement.js'
describe("<DevicePlatforms/>", function() {
it("with all devices selected renders all children", function() {
const value = {
device_platforms: "mobile/desktop",
}
const Component = <DevicePlatforms
value={value}
>
<PlacementOption/>
<PlacementOption/>
</DevicePlatforms>
const wrapper = shallow(Component)
expect(wrapper.find('PlacementOption')).toBe(2)
})
it("with no devices selected renders no children", function() {
const value = {}
const Component = <DevicePlatforms
value={value}
>
<PlacementOption/>
<PlacementOption/>
</DevicePlatforms>
const wrapper = shallow(Component)
expect(wrapper.find('PlacementOption')).toBe(0)
})
})
I have tried 'PlacementOption', PlacementOption in a find call.
All I get is a:
Expected ShallowWrapper({ many many lines of shallow wrapper content }) to be 3
Expected ShallowWrapper({ many many lines of shallow wrapper content }) to be 0
errors.
I can paste the "many many lines of shallow wrapper content" if needed but i don't think it is related and my problem is somewhere else - probably around somewhere around me not knowing how to use shallow render stuff.
You're asserting that an enzyme ShallowWrapper is equal to 3 or 0. This doesn't make sense.
Instead, the ShallowWrapper that is returned from .find() has a .length property. Try using that instead.
expect(wrapper.find('PlacementOption').length).toBe(2)

Testing react component with enzyme and expect fails

I am testing a React component with Enzyme, Mocha, and Expect. The test case is shown below:
import React from 'react';
import expect from 'expect';
import { shallow } from 'enzyme';
import Add from '../src/client/components/add.jsx';
describe('Add', () => {
let add;
let onAdd;
before(() => {
onAdd = expect.createSpy();
add = shallow(<Add onAdd={onAdd} />);
});
it('Add requires onAdd prop', () => {
console.log(add.props());
expect(add.props().onAdd).toExist();
});
});
I am creating a spy using expect and attaching it to the onAdd prop of the Add component. My test checks if the prop exists on the component. For some reason, onAdd is undefined and the test fails. Any help?
The problem is that add isn't wrapping the <Add> component, it wraps what it returns. So, if your component looks like:
class Add extends React.Component {
render() {
return (
<div>
{this.props.foo}
</div>
);
}
}
This statement add.props().onAdd will try to access onAdd prop from <div> not from <Add>, and obviously it will fail.
This assertion:
expect(add.props().onAdd).toExist();
Will succeed, in the component will look like:
class Add extends React.Component {
render() {
return (
<div onAdd={this.props.onAdd}>
{this.props.foo}
</div>
);
}
}
Example shown in enzyme docs, is a little bit confusing.

Resources