How to test child component method with Enzyme?
Asked Answered
K

12

30

I have a component like that:

<Parent>
  <Child/>
</Parent>

and <Child/> component have a method foo. I want test the foo method but I don't know how to access it. I tried:

mount(<Parent><Child/></Parent>).props().children.foo

or

mount(<Parent><Child/></Parent>).children().foo

but both them are undefined. I can't use .instance() because it's not root. I can't mount <Child/> only because the <Parent> add something (react-router's context.router) on context and I need them when init <Child/>. Any idea with this?

Krieg answered 15/2, 2017 at 9:21 Comment(3)
It's seems this is still not possible or at least complicated: github.com/airbnb/enzyme/issues/361Vagary
Test the react components lower down in the tree before working your way up. Also export an unwrapped version of your component for tests in order to remove the dependency of the component on react router.Sullage
+1 most annoying thing about writing react specs, I don't understand how you could write a spec framework for react and not have an easy way to access instances of child components. dumb.Sorce
S
6

This worked for me:

mount(<Parent><Child/></Parent>).find(Child).instance().foo
Shred answered 17/7, 2018 at 11:6 Comment(2)
Instance can only be called on root.Fadeless
how to import Child componentPeanut
D
4

I would consider writing tests for only your parent class, and then a separate test file to only test your child.

Once you have mounted you component using:

const component = mount(<Child>); 

you then have access to it's methods using:

component.instance().methodname

You can then do stuff like override it with jest.fn() and test appropriately.

Deedeeann answered 8/2, 2018 at 16:45 Comment(1)
This is not possible anymore from React 16.xPtosis
E
4

I prefer shallow mount over full mount from enzyme.

In conjunction with proxyquire to resolve child component (which you want to test) I do

wrapper.find('Child1').props().propName

And test it.

Or I use shallow

mount wrapper.dive()
Extreme answered 16/6, 2018 at 10:4 Comment(0)
I
2

I think your problem is way different from how to test child components.

My first question is: Why are you checking if a child component has a specific method in the parent's component tests?

IMHO you need to have a test specific for this component and, then, in this test you check if the method exists.

Just to not leave without the answer, did you tried .find(Child).instance().foo ?

Idette answered 8/2, 2018 at 16:40 Comment(1)
.instance() can only be called on the "root"Leftover
W
2

I had a similar problem when trying to mock a function on an inner component within a MemoryRouter:

cont wrapper = mount(<MemoryRouter><AvailabilityButtonWithRouter.WrappedComponent vetId={ vetId  } appointment={ availability } /></MemoryRouter>);     

I ended up being able to mock the function like so:

const mockFn = jest.fn();    
wrapper.children(0).children(0).instance().reloadCurrentPage = mockFn;
Weathering answered 16/6, 2018 at 5:31 Comment(1)
Good, it helped me find a function in 3 level nested Item child component that rendered multiple times. Don't need the 0 argument, like .children(0), but just children(). And when ready to find it, just .first().getElement() and props. My Example : wrapper.find('Item').children().first().getElement()Herefordshire
M
2

I was able to get a handle on child function like the following, i was looking for the first child to call the function on -

const component = shallow(<Component />);
component.find(Child).first().getNode().props.someChildFunction()
Mcminn answered 17/7, 2018 at 7:50 Comment(0)
L
0

I faced a similar problem and I went through mount API by logging. In my use case, my child component(CommonStoresReactions) is wrapped with mobx inject.

const jsx = (
    <Provider {...stores}>
      <CommonStoresReactions>
        <div />
      </CommonStoresReactions>
    </Provider>
)
const wrapper = mount(jsx)

I want to test clearStores method in CommonStoresReactions. Below snippet worked for me.

wrapper
      .find(CommonStoresReactions)
      .instance()
      .wrappedInstance.clearStores()
Luellaluelle answered 20/12, 2018 at 10:21 Comment(1)
.instance() can only be called on "root"Leftover
H
0

Enzyme has an option for the mount API called wrappingComponent (and wrappingComponentProps) to wrap the mounted object inside another component for providing context, etc.

See https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#mountnode-options--reactwrapper

Hash answered 11/12, 2019 at 7:17 Comment(0)
E
0

I managed to solve this by using dive

wrapper.dive().props().propName
Erikaerikson answered 18/8, 2020 at 5:32 Comment(0)
B
0

With enzyme:

mount(<Parent><Child/></Parent>).childAt(0).instance().foo

There are valid reasons to access the child and call a method. If the parent is superficial and children have a consistent interface you can call methods without knowing which child it is, testing that all children have the correct interface, signature etc.

Basis answered 13/10, 2020 at 18:33 Comment(0)
P
0

The best way I find out is using shallow wrapper's dive method. Here is the doc: enzyme dive doc

Remember if ur parent component use the fully rendering like mount, then the react wrapper itself doesnt have the dive method so u have to use shallow render.

Here is one example:

let instance, child, diveChild;
describe('Test Parent child Child component', () => {
  beforeEach(() => {
    wrapper = shallow(<Parent {...props} />);
    child = wrapper.find('Child');
    diveChild = child.dive();
    console.log(diveChild.instance());
  });

  test('Child get mounted', () => {
    expect(child.exists()).toBeTruthy();
    expect(child.debug()).toMatchSnapshot();
  });
});
Palikar answered 1/6, 2021 at 14:46 Comment(0)
H
0

I would start by echoing @rnmalone's answer that you probably don't want to test a function on a child directly. That's not really unit testing, that's integration testing. You don't want to test your foo method.

That said, you may want to grab children to test their bar value and see if they received something you did to them by manipulating the parent. And since, if you mount at least (?), the children are fully instantiated, there's no reason to spy on a shim; you can go straight to the "real" child and test it.

Here's a test that does both. We do test a foo on a child, so to speak -- it's an event handler -- and then we test its bar value -- in this case that the proper child had a value set to match what we raised in the foo event.


Testing if foo sets bar

In this test, we've spun up a component we're testing (we were using Preact instead of React and htm in place of JSX; apologies if I don't clean this perfectly):

var wrapper = Enzyme.mount(
    <MyParentComponent
        myItemTypes={arrayTypes}
        mySelectedItem={someSelectedItem}
        onTabClicked={mySpy.onTabClicked}
    />
);

Now MyParentComponent has child components in its markup called MyChildComponent.

(This is its "live" code, returned by a functional component, and is not from a test file, to be overly clear.)

return <ul>
    {Object.keys(props.myItemTypes).map(function (key) {
        var isSelected = myItemTypes[key] === mySelectedItem;

        return 
            <MyChildComponent
                isSelected={isSelected}
                tabType={myItemTypes[key]}
                onTabClicked={props.onTabClicked}
            ></MyChildComponent>
        ;
    })}
</ul>;

So the parent component is, with respect to the onTablClicked event handler, basically just a passthrough.

Now I can spoof a click on a child item like this using ReactTestUtils:

var specificItem = wrapper
    .find('MyChildComponent')
    .findWhere((x) => x.props().tabType.value === newlySelectedTab.value);

if (specificItem) {
    var onTabClicked = lessonsTab.props().onTabClicked;
    TestUtils.act(function () {
        onTabClicked(newlySelectedTab);
    });
}

wrapper.update();

The nasty part here is that I registered that onTabClicked from this on the parent component:

onTabClicked={mySpy.onTabClicked}

That is, that act on the selected child will just call my spy, and my spy does this:

spyOn(mySpy, 'onTabClicked').and.callFake(function (tab) {
    wrapper.setProps({ mySelectedItem: tab });
});

That's problematic. We'll discuss that later.

Now I can run a test to see if the child's prop was updated.

it('should send the new selection value to the child component', function () {
    var allItems = wrapper.find(MyChildComponent);
    var selectedItem = navItems.findWhere((x) => x.props().isSelected);

    expect(selectedItem.props().settingsTab.value).toBe(newlySelectedTab.value);
});

But that really reduces to mock foo and real bar

Again, the weird part of doing that is the fake click on the child is really close to testing your mocks, which you shouldn't really do. (Reference is a powerpoint. Click at your own risk.)

powerpoint slide titled "Common testing mistakes & avoiding them" with a first point of "don't test your mocks (surprisingly common)"

I could've just tested initial state setting like this:

it('should send the new selection value to the child component', function () {
    // Act 
    wrapper.setProps({ mySelectedItem: itemToSelect });
    wrapper.update();

    // Assert
    var allItems = wrapper.find(MyChildComponent);
    var selectedItem = navItems.findWhere((x) => x.props().isSelected);

    expect(selectedItem.props().tabType.value).toBe(itemToSelect.value);
});

... and that's probably good enough for whatever work you're doing. It reduces to nearly the same thing.

I guess the bonus is that you know the event handler is registered on the child, and that's something. Though we should probably just test that foo fired using mySpy on MyParentComponent, right? And then add a second test to see that the child value changes when the parent prop does.

So likely still a code smell somewhere.

Halloween answered 20/10, 2022 at 15:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.