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.)
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.