I have a component and I want to test the click method. I am using shallow but my test is failing as it cannot find the button and hence it`s click method. What is wrong with my code?
interface IProps {
label: string;
className: string;
onClick: () => void;
}
export const NewButton: React.StatelessComponent<IProps> = props => {
return (
<Button type="button" className={props.className} onClick={props.onClick}>
{props.label}
</Button>
);
};
import { shallow } from 'enzyme';
import * as React from 'react';
import { NewButton } from "../Buttons";
describe('<NewButton />', () => {
describe('onClick()', () => {
const props = {
className: "buttonSubmit",
label: "submit",
onClick: () => {},
}
test('successfully calls the onClick handler', () => {
const mockOnClick = jest.fn();
const wrapper = shallow(
<NewButton {...props} />
);
const button = wrapper.find('submit').dive();
expect(button.exists()).toEqual(true)
button.simulate('click');
expect(mockOnClick.mock.calls.length).toBe(1);
});
});
});
Since you are using shallow method, it will only render the component that we are testing. It does not render child components. So you should try to find the Button component.
const button = wrapper.find('Button');
After that you should mock the props.onClick event handler passed as props to NewButton component.
const props = {
className: "buttonSubmit",
label: "submit",
onClick: jest.fn(),
}
So you can use
describe('<NewButton />', () => {
describe('onClick()', () => {
const props = {
className: "buttonSubmit",
label: "submit",
onClick: jest.fn(),
}
test('successfully calls the onClick handler', () => {
const wrapper = shallow(
<NewButton {...props} />
);
const button = wrapper.find('Button');
expect(button.exists()).toEqual(true)
button.simulate('click');
// Since we passed "onClick" as props
// we expect it to be called when
// button is clicked
// expect(props.onClick).toBeCalled();
expect(props.onClick.mock.calls.length).toBe(1);
});
});
});
Related
I have a component which imports a custom hook. I want to mock returned values of this hook but ensure the useState still works when I fire and event.
component.tsx
export const Component = () => {
const { expanded, text, handleClick, listOfCards } = useComponent();
return (
<div>
<button id="component" aria-controls="content" aria-expanded={expanded}>
{text}
</button>
{expanded && (
<div role="region" aria-labelledby="component" id="content">
{listOfCards.map((card) => (
<p>{card.name}</p>
))}
</div>
)}
</div>
);
};
useComponent.tsx
const useComponent = () => {
const [expanded, setExpanded] = useState(false);
const { listOfCards } = useAnotherCustomHook();
const { translate } = useTranslationTool();
return {
text: translate("id123"),
expanded,
handleClick: () => setExpanded(!expanded),
listOfCards,
};
};
component.test.tsx
jest.mock("./component.hook");
const mockuseComponent = useComponent as jest.Mock<any>;
test("Checks correct attributes are used, and onClick is called when button is clicked", () => {
mockuseComponent.mockImplementation(() => ({
text: "Click to expand",
listOfCards: [{ name: "name1" }, { name: "name2" }],
}));
render(<Component />);
const button = screen.getByRole("button", { name: "Click to expand" });
expect(button).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(button);
expect(button).toHaveAttribute('aria-expanded', 'true');
});
With the above test aria-expanded doesnt get set to true after we fire the event because im mocking the whole hook. So my question is, is there a way to only mock part of the hook and keep the useState functionality?
Basically the title.
Here is the overview of the App:
const App = () => {
const [isViewFavoriteImages, setIsViewFavoriteImages] = useState(false);
const toggleIsViewFavoriteImages = () => {
setIsViewFavoriteImages(
(prevToggleIsViewFavoriteImagesState) =>
!prevToggleIsViewFavoriteImagesState
);
};
return (
<div className="App">
<div className="container">
<ToggleImagesViewButton
toggleIsViewFavoriteImages={toggleIsViewFavoriteImages}
isViewFavoriteImages={isViewFavoriteImages}
/>
<ImageList isViewFavoriteImages={isViewFavoriteImages} />
</div>
</div>
);
};
export default App;
The button component:
export interface ToggleImageViewButtonProps {
toggleIsViewFavoriteImages: () => void;
isViewFavoriteImages: boolean;
}
const ToggleImageViewButton: React.FC<ToggleImageViewButtonProps> = ({
toggleIsViewFavoriteImages,
isViewFavoriteImages,
}) => {
return (
<button
onClick={toggleIsViewFavoriteImages}
className="btn btn_toggle-image-view"
data-testid="toggle-image-view"
>
{isViewFavoriteImages ? "view all" : "view favorites"}
</button>
);
};
export default ToggleImageViewButton;
And this is how I am testing it:
function renderToggleImagesViewButton(
props: Partial<ToggleImageViewButtonProps> = {}
) {
const defaultProps: ToggleImageViewButtonProps = {
toggleIsViewFavoriteImages: () => {
return;
},
isViewFavoriteImages: true,
};
return render(<ToggleImageViewButton {...defaultProps} {...props} />);
}
describe("<ToggleImagesViewButton />", () => {
test("button inner text should change to 'view all' when the user clicks the button", async () => {
const onToggle = jest.fn();
const { findByTestId } = renderToggleImagesViewButton({
toggleIsViewFavoriteImages: onToggle,
});
const toggleImagesViewButton = await findByTestId("toggle-image-view");
fireEvent.click(toggleImagesViewButton);
expect(toggleImagesViewButton).toHaveTextContent("view favorites");
});
});
This test fails and "view all" is still getting returned.
ToggleImageViewButton doesn't have internal state - the state was lifted to the parent, so testing state changes should happen in the parent's tests.
You could have the following integration test to verify the correct behaviour of the button when used in App.
test("App test", () => {
render(<App />);
const button = screen.getByTestId("toggle-image-view");
expect(button).toHaveTextContent("view favorites");
fireEvent.click(button);
expect(button).toHaveTextContent("view all");
});
As for the ToggleImageViewButton unit tests, you can simply test that it renders the right text based on isViewFavoriteImages value, and that the callback gets called when the button is clicked.
test("ToggleImageViewButton test", () => {
const onToggle = jest.fn();
render(<ToggleImageViewButton isViewFavoriteImages={false} toggleIsViewFavoriteImages={onToggle}/>);
expect(screen.getByTestId("toggle-image-view")).toHaveTextContent("view favorites");
fireEvent.click(screen.getByTestId("toggle-image-view"));
expect(onToggle).toHaveBeenCalled();
});
I am running a test on the simple button component. All works fine, however, in the test coverage I am getting an untested line (21: onClick: () => { return true }). This line points to the default props function. How can I make sure that I have covered the unit test for this line?
My component:
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
export const Button = (props) => {
return (
<Link
to={props.link}
className={props.classes}
onClick={props.onClick}
>
{props.title}
</Link>
);
};
Button.defaultProps = {
link: '/',
title: 'Home',
classes: 'btn btn--primary',
onClick: () => { return true }
}
Button.propTypes = {
link: PropTypes.string,
title: PropTypes.string,
classes: PropTypes.string,
onClick: PropTypes.func
};
export default Button;
My test:
import React from 'react';
import { shallow } from 'enzyme';
import { Button } from '../../../components/partials/Button';
test('should render button', () => {
const wrapper = shallow(<Button link='/test' title='Home' classes='btn btn--primary' onClick={ () => { return true }} />);
expect(wrapper).toMatchSnapshot();
const props = wrapper.props();
expect(props.to).toBe('/test');
expect(props.className).toBe('btn btn--primary');
expect(props.onClick).toBe(props.onClick);
expect(props.children).toBe('Home');
});
test('should have default onClick', () => {
expect(Button.defaultProps.onClick).toBeDefined();
});
You need to run the onClick function in order for it to count for coverage. So, make a test that simulates a click event on the button, which will trigger its onClick function:
it('does something when clicked', () => {
const wrapper = shallow(<Button link='/test' title='Home' classes='btn btn--primary' onClick={ () => { return true }} />);
wrapper.simulate('click');
expect(...);
});
I am trying test a component in React w/ TypeScript using Jest and Enzyme.
My test is as follows:
import * as React from 'react';
import { shallow } from 'enzyme';
import * as sinon from 'sinon';
import { ButtonGroup } from '../../../src/components';
describe('.ButtonGroup', () => {
it('should render', () => {
const { wrapper } = setup({});
expect(wrapper.exists()).toBe(true);
});
it('should call the rightHandler handler on click', () => {
const onClickHandler = sinon.spy();
const wrapper = setup({ rightHandler: onClickHandler });
wrapper.find('a').simulate('click');
expect(onClickHandler).toHaveBeenCalled();
});
});
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return { wrapper };
};
const MOCK_LEFT = { type: 'reset', className: 'is-light', value: 'Reset' };
const MOCK_RIGHT = { type: 'button', className: 'is-primary', value: 'Search' };
However I am getting an error: TypeError: wrapper.find is not a function
The component I am testing looks like
import * as React from 'react';
const ButtonGroup = ({ leftBtn, rightBtn, leftHandler = null, rightHandler = null }) => (
<div className="field is-grouped is-grouped-right">
<p className="control">
<input type={leftBtn.type} className={'button ' + leftBtn.className} value={leftBtn.value} onClick={leftHandler} />
</p>
<p className="control">
<input type={rightBtn.type} className={'button ' + rightBtn.className} value={rightBtn.value} onClick={rightHandler} />
</p>
</div>
);
export default ButtonGroup;
I would like to essentially asset that on click, the action I expect is called.
Try
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return wrapper;
};
Doing return { wrapper } is the same as return { wrapper: wrapper } so it's not your DOM element anymore, but an object with a property wrapper containing your DOM element.
This is a small bin to illustrate the issue.
I've got a button in React which opens a simple confirmation window when the user clicks on it. Before I added the confirm method, the test below was green. After adding the confirm it's red. How do I need to change the test to work with the additional confirm?
React delete button:
const DeleteButton = (props) => {
const handleDelete = () => {
if(confirm("Are you sure?")) {
props.onDelete(props.id)
}
};
return (
<Button className="btn" onClick={handleDelete}>
<i className="fa fa-trash-o"></i>
</Button>
);
};
Here is the test (using enzyme):
describe('<DeleteButton />', () => {
it("deletes the entry", () => {
const onDelete = sinon.spy();
const props = {id: 1, onDelete: onDelete};
const wrapper = shallow(<DeleteButton {...props} />);
const deleteButton = wrapper.find(Button);
deleteButton.simulate("click");
expect(onDelete.calledOnce).to.equal(true);
});
});
You can stub confirm using sinon.stub.
describe('<DeleteImportButton />', () => {
it("simulates delete event", () => {
const onDeleteImport = sinon.spy();
const props = {id: 1, onDelete: onDeleteImport};
const wrapper = shallow(<DeleteImportButton {...props} />);
const deleteButton = wrapper.find(Button);
const confirmStub = sinon.stub(global, 'confirm');
confirmStub.returns(true);
deleteButton.simulate("click");
expect(confirmStub.calledOnce).to.equal(true);
expect(onDeleteImport.calledOnce).to.equal(true);
confirmStub.restore();
});
});