Mock an imported Component that is passed in as a prop - javascript

I have a component ExternalComponent which comes from a different module fetched via another
configuration js file. This is then passed in as a prop into the Component I am testing as follows.
const LocalComponent = ({
externalComponent: ExternalComponent, // imported Component coming in as a prop
}) => {
const onChange = (index) => {
console.log('test);
SetData1();
SetData2();
};
return (
<div className={styles.ExternalComponent}>
<ExternalComponent
onChange={onChange}
/>
</div>
);
};
export default LocalComponent;
I am trying to test that the onChange works via following test using mount.
But unable to do so ending up with following error.
Uncaught [Error: mockConstructor(...): Nothing was returned from
render. This usually means a return statement is missing.
shallow works fine but I am looking to do the test via mount as I am checking the onChange.
Is there a way around this? Cos I can't just mock it as follows since this is not a Component inside my project.
// not gonna work, is an imported Component, not in my paths.
jest.doMock('./ExternalComponent', () => {
const ExternalComponent = () => <div />;
return ExternalComponent;
});
This is the test that is failing with above error.
import React from 'react';
import { shallow, mount } from 'enzyme';
import LocalComponent from './LocalComponent';
describe('LocalComponent', () => {
const mockSetData1 = jest.fn();
const mockSetData2 = jest.fn();
const mockExternalComponent = jest.fn();
const defaultProps = {
externalComponent: mockExternalComponent,
SetData1: mockSetData1,
SetData2: mockSetData2
};
const shallowRender = props => shallow(<LocalComponent {...defaultProps} {...props} />);
const mountRender = props => mount(<LocalComponent {...defaultProps} {...props} />);
// works fine since using shallow
it('should render', () => {
const rendered = shallowRender();
expect(rendered).toMatchSnapshot();
});
// FAILING TEST
it('should render via mount too', () => {
const rendered = mountRender(); // this breaks with above error
// trying to achieve following test, currently unavailable.
rendered.find('.ExternalComponent').simulate('click');
expect(mockSetData1).toHaveBeenCalled();
expect(mockSetData2).toHaveBeenCalled();
});
});

In the ExternalComponent props, it seems that your function onChange is triggered when External's function onChange is triggered. I assume that the onChange function in the ExternalComponent is linked to an input, with a value prop, and that the methods SetData1 and SetData2 come from props. (Aren't they missing in your LocalComponent props ?)
To trigger it, in your test, you should simulate the "change" action rather than the "click" action in your ExternalComponent.
Updating this, you should have the following:
it('should render via mount too', () => {
const wrapper = mount(<LocalComponent {...defaultProps} />
wrapper.find('.ExternalComponent').simulate('change', { target: { value: "newValue" } });
expect(mockSetData1).toHaveBeenCalled();
expect(mockSetData2).toHaveBeenCalled();
});

Related

How to test a function inside React functional component using jest.spyOn with React Testing Library

This is a simple React component. I'm using React Testing Library for unit testing components and I was not able to test the handleClick function of TestComponent using jest.spyOn(). Could anyone please help?
Component
import React from 'react';
const Button = (props) => (
<button onClick={props.handleClick}> {props.text}</button>
);
const TestComponent = () => {
const handleClick = () => {
console.log('clicked!');
};
return (
<div>
<Button text="Test Button" handleClick={handleClick} />
</div>
);
};
export default TestComponent;
Test
it('expect spy to be called', () => {
const { getByText } = render(<TestComponent />);
const spy = jest.spyOn(TestComponent, 'handleClick');
const button = getByText('Test Button');
fireEvent.click(button);
expect(spy).toHaveBeenCalled();
});
The error that I'm getting is below. I tried using TestComponent.prototype in jest.spyOn but that does not help either.
Cannot spy the handleClick property because it is not a function; undefined given instead
You can't spy handleClick event handler since it's defined in functional scope, it's private.
Your best test component behavior rather than implementation, such as what method is called inside the event handler, you should not go to mock and assert whether these methods are called, but what changes have occurred, such as what happens to the component, UI, DOM tree, etc.
But in your example, the handleClick event handler does not do anything, just calls console.log, then you can only assert whether the console.log is called indirectly assert whether the handleclick is called.
// arrange
const logSpy = jest.spyOn(console, 'log')
// act
// ...
// assert
expect(logSpy).toHaveBeenCalled()

How to test a click event on a function sent through prop drilling

I have a click event in my component that I am trying to test with #testing-library/react. This click event is being issued a function from the parent component, like so:
<DownloadButton>
{handleDownload => (
<ActionButton
onClick={handleDownload}
data-testid={CONFIG.TEST_IDS.BUTTONS.DOWNLOAD}
>
Download
</ActionButton>
)}
</DownloadButton>
I can getByText and fireEvent.click on the button, but unsure how to test if the handleDownload function actually fired.
So if I understand your question correctly you can't be sure if the onClick handler is called when you press your ActionButton?
another case that you want to test is if the DownloadButton provides the handleDownload render prop.
I would split the one test into two tests and separate each component on its own.
import React from "react";
import { DownloadButton, ActionButton } from "./App";
import { render, fireEvent } from "#testing-library/react";
describe("DownloadButton", () => {
it("returns handleDownloadFunction", () => {
const childrenMock = jest.fn();
render(<DownloadButton children={childrenMock} />);
expect(childrenMock).toHaveBeenCalledTimes(1);
expect(childrenMock.mock.calls[0][0].handleDownload).toBeDefined();
});
});
describe("ActionButton", () => {
it("onClick invokes function", () => {
const onClickMock = jest.fn();
const { getByTestId, debug } = render(
<ActionButton onClick={onClickMock} data-testid={"test-button"} />
);
debug();
const button = getByTestId("test-button");
fireEvent.click(button);
expect(onClickMock).toHaveBeenCalledTimes(1);
});
});
for more detail take a look at the codesandbox

Is there a way to unit test an innaccessible callback function that is called from a child React component using jest

I am unit testing a React 16 functional component that defines a callback and passes it as a property down to the next component to be invoked when a click event fires.
import React from 'react'
function OuterComponent() {
const handleClick = useCallback(() => {
openDialog() // <--- This code is inaccessible from my "OuterComponent.test.js" unit test
}
return <MyDialog clickHandler={handleClick} />
}
export default OuterComponent
I am seeking 100% test coverage and would like to mock all other components/functions using jest that are not part of the "OuterComponent" component.
The problem I am having is that I can't seem to mock the MyDialog component in order to have it trigger the handleClick function.
I also tried to mock the component using this snippet but it still seems to try to load all the imports from MyDialog
jest.doMock('components/MyDialog', () => {
return () => () => <div />
})
//... other mocks along with **openDialog**
//...and in my describe/it...
MyDialog.mockImplementation(() => {})
OuterComponent().handleClick()
expect(openDialog).toHaveBeenCalled()
The hope here was that I could render an empty div for MyDialog and just simply call the handleClick function to test it. Does anyone know what I could be doing wrong or have any other ideas of how to test it?
I don't think you can mock a var in a closure. The best you can do is something along those lines:
import { fireEvent, render } from 'react-testing-library';
jest.mock(
'components/MyDialog',
() => ({ clickHandler }) => (
<button
onClick={clickHandler}
test-id="button"
>
Click me
</button>
),
);
it('should call clickHandler', () => {
const onClick = jest.fn(() => null);
const { queryByTestId } = render(<MyDialog clickHandler={onClick} />);
fireEvent.click(queryByTestId('button'));
expect(onClick.toHaveBeenCalledTimes(1));
});
You actually test the ability for you MyDialog to render something that can execute a callback.

React Testing if Hook State was Updated by a Child Component

I've read several articles on testing React Hooks and it seems the general advice is not to directly test state. We want to be testing things that the user will actually see. In my situation I have a hook which is either true or false. This hook will determine what gets rendered in my component. Also this hook is passed as a prop to a child which is where the state change will occur.
I am looking for a way to just set the initial hook state in my test so that I don't have to go through rendering child components and context in my test just to click a button.
The parent component has the following hook and function:
export const AssignRoles = props => {
let [openForm, setOpenForm] = useState(false);
const handleFormOpen = (type) => {
setOpenForm(openForm = !openForm);
};
return (
<div>
<div>
{openForm ? <Component /> : < OtherComponent formOpen={handleFormOpen}/>}
</div>
</div>
);
};
The hook openForm initially is false so the <OtherComponent> loads and takes our hook updater function as a prop.
What I want to do is write a test that checks what renders when openForm = true
I have tried a few tests like this:
it('renders <Component/>', () => {
let openForm = true
const wrapper = mount(<AssignRoles openForm={openForm}/>);
expect(wrapper).toContain(<Component/>);
});
but haven't been successful.
Add the initial state as a prop value:
export const AssignRoles = ({initialOpenForm = false}) => {
let [openForm, setOpenForm] = useState(initialOpenForm);
const handleFormOpen = () => {
setOpenForm(!openForm);
};
return (
<div>
<div>
{openForm ? <Component /> : < OtherComponent formOpen={handleFormOpen}/>}
</div>
</div>
);
};
Then in your test:
it('renders <Component/>', () => {
const wrapper = mount(<AssignRoles initialOpenForm={true}/>);
expect(wrapper).toContain(<Component/>);
});

How to check the value of a nested React component in a unit test with Enzyme and Jest

I'm new in testing and I would like to access to a const
const Label = ({ intl, data }) => {
if (data && data.length === 0) {
return <div>{intl.translate('no_data')}</div>
}
return null
}
The test file:
test('should return null when is data', () => {
const component = shallow(<StatisticsGraph {...mockPropsForComponent} />)
const label = component.find(Label)
expect(label).toEqual(null)
})
The variable mockPropsForComponent has the variable data with some values.
I want to know the value of Label for the test pass
There are different solutions, for different problems to solve.
Testing an isolated component
Isolate the Label component in its own file and export it.
const Label = ({ intl, data }) => {
if (data && data.length === 0) {
return <div>{intl.translate('no_data')}</div>
}
return null
};
export default Label;
Then, test it individually. Like Quentin mentioned, a component is just a function you can call. We could call it directly, but it would leak irrelevant implementation details to the test. Instead, we assume it's a component and test it as such.
import Label from './Label';
describe('Label component', () => {
it('returns null when there is data', () => {
// works but leaks implementation details
expect(Label({ data: ['anything'] })).toEqual(null);
// Implementation agnostic test
const wrapper = shallow(<Label />);
expect(wrapper.isEmptyRender()).toBe(true);
});
});
Testing a composition of components
Since you're testing StatisticsGraph and that your Label component has no identifiable selector, you could use snapshots to make sure it's rendered correctly.
import React from 'react';
import { render } from 'enzyme';
import toJson from 'enzyme-to-json';
describe('StatisticsGraph component', () => {
it('renders properly', () => {
const wrapper = render(<StatisticsGraph {...mockPropsForComponent} />);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
The snapshot artifact should be committed alongside code changes, and reviewed as part of your code review process. [...] On subsequent test runs Jest will simply compare the rendered output with the previous snapshot. If they match, the test will pass. If they don't match, either the test runner found a bug in your code that should be fixed, or the implementation has changed and the snapshot needs to be updated.
Testing a component manually
If we really want to test each component's part manually, we may want to change the Label to be easier to find.
const Label = ({ intl, data }) => (
<div className="label">
{Boolean(data && data.length === 0) && intl.translate('no_data')}
</div>
);
Then, the component should be found and we can use .isEmpty() to check that it worked.
describe('StatisticsGraph component', () => {
it('does not render a label when there is data', () => {
const component = shallow(<StatisticsGraph {...mockPropsForComponent} />);
const label = component.find(Label);
expect(label.isEmpty()).toBe(true);
});
});

Categories