React Testing Library: OnClick isn't working in a child component - javascript

I have a test like this:
it('Side effect should trigger', async ()=>{
await act(async () => {
let container = render(<Grandparent/>);
const btn = container.findByTestId('childBtn');
fireEvent.click(btn)
});
const changedMsg = await container.findByText('Changed');
expect(changedMsg).toBeInTheDocument();
})
My components look like this:
Grandparent:
const Grandparent = ()=>{
const [labelText, setLabelText] = useState('Unchanged');
return (<Parent labelText={labelText} setLabelText={setLabelText}/>);
}
Parent:
const Parent = ({labelText, setLabelText})=>{
return (<div>
<label>{labelText}</label>
<Child setLabelText={setLabelText} />
</div>)
}
Child:
const Child = ({setLabelText}) =>{
return(<div>
<Button data-testid='childBtn' onClick={()=>setLabelText('Changed')}/>
</div>)
}
My issue is that the assertion in my unit test fails. I've logged the return value from findByTextId('childBtn') and I am indeed getting a button (something like <button class='MuiButtonBase-root' data-testid='childBtn'></button>), but the side effect from clicking it is not going through. I added some logging in the onClick function and it never showed up when I re-ran the test, so the issue is that the onClick handler isn't firing at all.
How do I fix this? Is trying to test something that involves side effects from a bunch of nested components like this not feasible in the first place?

EDIT: I think you have some code mistakes in your unit test since you are mixing react-testing-library API and testing-library, try this:
it("Side effect should trigger", async () => {
const {findByTestId, findByText} = render(<Grandparent />);
fireEvent.click(await findByTestId("childBtn"));
const changedMsg = await findByText("Changed");
expect(changedMsg).toBeInTheDocument();
});
});
I did not test it, but it should work.
act() wrapper is not needed in react-testing-library since the render method is using it internally.
With react-testing-library you dont' have to care about implementation details of your components, nor you care to test internal state, etc... You just test "What the end user sees on screen in response to certain events". If you need to test functions, like fetch, interactions with global object, etc... you need to mock all those methods.
The philosophy of this approach is to make your unit tests ( which are never funny to write ) so that they won't have to change too often even if you are going to refactor your React Components. You just care about what it' s being rendered as DOM nodes and that's what you have to test.
NOTE: In the code you pasted there are typos btw, props have to be destructured in your code if you want to use them directly without doing props.prop, like : const Child = ({setLabelText}) =>{ .

Related

How to test the reaction to a component event in Svelte?

In Svelte, I have a parent component which listens to a component event dispatched by a child component.
I know how to use component.$on to check that the dispatched event does the right thing within the component which is dispatching, like so.
But I can't figure out how to check that the component which receives the dispatch does the right thing in response.
Here's a basic example:
Child.svelte
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
function handleSubmit(event) {
dispatch('results', 'some results')
}
</script>
<form on:submit|preventDefault={ handleSubmit }>
<button type='submit'>Submit</button>
</form>
Parent.svelte
<script>
import Child from './Child.svelte'
let showResults = false
function handleResults(event) {
showResults = true
}
</script>
<Child on:results={ handleResults } />
{ #if showResults }
<p id='results'>Some results.</p>
{ /if }
The idea is to eventually write a test using #testing-library/svelte like:
import { render } from '#testing-library/svelte'
import Parent from './Parent.svelte'
test('shows results when it receives them', () => {
const rendered = render(Parent)
// ***
// Simulate the `results` event from the child component?
// ***
// Check that the results appear.
})
If the parent were reacting to a DOM event, I would use fireEvent.
But I don't know how I would get a hold of the <Child> component in this case, and even if I could I'm guessing that Svelte is using a different mechanism for component events.
(Just to test it out, I used createEvent to fire a custom results event on one of the DOM elements rendered by <Child> but it didn't seem to do anything.)
Anyone have any ideas? Thanks!
If you're already planning on using #testing-library/svelte, I think the easiest way is not to try to manually trigger the Child component's results event, but to use Testing Library to grab the form/submit elements and trigger the submit event (using fireEvent a SubmitEvent on the <form> or their #testing-library/user-event library, or even a vanilla dispatchEvent). Svelte would then dispatch the custom results event that Parent is listening on.
Something like:
test('shows results when it receives them', async () => {
// Arrange
const rendered = render(Parent)
const submitButton = rendered.getByRole('button', {
name: /submit/i
});
const user = userEvent.setup();
// Act
await user.click(submitButton);
// Assert
const results = rendered.queryByText(/some results\./i);
expect(results).not.toBe(null);
});
Hope this is what you had in mind.
Edit:
For mocking Child.svelte, something like this in a __mocks__/Child.svelte should work:
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function handleSubmit(event) {
dispatch("results", "some results");
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<button type="submit">Test</button>
</form>
Which is the exact same implementation as the actual module (I gave the button a different label just to make it clear it's the mocked version when querying it), but the idea is that this would never need to change and is only used to dispatch a results event. Then you'd just need to tell Jest or whatever you're using that you're mocking it (jest.mock("./Child.svelte");), change the getByRole query to match the new name (or just leave the mock with the original name), then it should just work.
Whether you think that's worth it or not is up to you. I've generally had success testing the UI as a whole rather than mocking sub-components, but I guess it comes down to preference. Yes, you might have to change the test if the Child component changes, but only if you change the label of the button or change the user interaction mechanism.
You don't need to know about the details of the components, you don't even need to know that it's split into a separate Child component, all the test would care about is a general idea of the structure of the UIā€”that there's a button called "Submit" and that clicking on it should show an additional <p> tag.

Update state of a immutable component from parent component

I am developing for a use case where I need to slightly change a pre existing component. This is the code for the preexisting component, but I can not change the ABC.tsx file, can just import it.
export const ABC: React.FC<IABC> = (props) => {
const [selectedKey, setSelectedKey] = useState('0');
return ();
};
Now, the use case is from the calling component I want to change the selectedKey state of ABC to a different value say '1'. Is there a way to do so in react? I can use the ABC to create clones/wrapper components but not sure how to proceed with modifying states of ABC. Any leads would really help.
you will have to tamper with the ABC.tsx file, maybe do something like this
export const ABC: React.FC<IABC> = (props) => {
const [selectedKey, setSelectedKey] = useState(props.abc_integer);
return ();
};
Otherwise there is no other way to do it, even if you run a clone or do some React children on, you will still need to tamper. get whoever is on your team that wrote component ABC to change it to suit what you want or you simple clone its code and run it through a wrapper.
psa: As far as I know, any other method is anti-react pattern

Testing an isolated function of a component in React Jest

I am fairly new to Jest and Enzyme and I stumbled a cross a problem:
I have a Component which renders Children and also calls a method of those children. I achieve that by using refs. I call these functions something like:
somefunction = () => {
this.myReference.current.childFunction();
this.doSomethingOther();
}
I now want to test the function somefunction. I want to check if the doSomethingOther function was called. Using a shallow render I cannot achieve that. The test would succeed if this.myReference.current.childFunction(); wasn't called. Jest cannot know it because it only renders shallow and therefore throws an error.
I may be missing the full understanding. I wonder if someone has a Idea to test this function without using mount.
Take a look at the below code where I shallow render a component and then get the class instance and mock the required functions. Now when we call somefunction, we check if doSomethingOther has been called. (Assumption you are using jest+enzyme)
const wrapper = shallow(<Comp />);
const inst = wrapper.instance();
inst.myReference = {
current: {
childFunction: jest.fn()
}
}
inst.doSomethingOther = jest.fn();
inst.somefunction();
expect(inst.doSomethingOther).toHaveBeenCalled();

Covering Anonymous Function Arguments in Unit Tests

I have multiple situations in code where we pass a function as an argument when creating an object (like so in onPress):
<Touchable
onPress={() => Linking.openURL(formatUrl(url))}
noContainer={true}>
{children}
</Touchable>
When it comes down to it, this is a snippet in a larger component getting rendered. As it's not actually a method of the component, I'm not especially interested in testing it here - it will get tested as part of another component if needed.
However, the code coverage report comes back with an indication that the function is uncovered.
Is there a way to satisfy this coverage - either by testing this function, or ignoring all functions passed in such a way (anonymously, as arguments)?
Assuming you are using Jest, you can set your Jest mock to call the argument that it receives, so:
const componentProps = {
onPress: jest.fn(func => func());
}
So the mocked onPress function, in this case, will receive your anonymous function and call it. Coverage will show that the anonymous function was called.
Hope it helps :)
First, I'd like to advise against exempting lines from coverage, especially if you're being paid to write this code and unit test it.
One thing I've found that works is to write a void function that calls your function with your desired arguments.
if you have:
onClick={ () => function(argument) }
and Jest isn't covering it, try:
const functionWithArgs = () => function(argument)
onClick={functionWithArgs}
I use this a lot for modals:
const [isModalOpen, setModalOpen] = React.useState(false)
const toggleModal = () => setModalOpen(!isModalOpen)
onClick={toggleModal}
But the same principle can be applied to a much more complex function. And it works!

How to test child component method with Enzyme?

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?
This worked for me:
mount(<Parent><Child/></Parent>).find(Child).instance().foo
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.
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()
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 ?
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;
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()
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()
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
I managed to solve this by using dive
wrapper.dive().props().propName
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.
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();
});
});
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.

Categories

Resources