Jest test failing and not receiving toHaveBeenCalled from simulated click - reactjs

I am having an issue with a Jest test using React. I am also using Enzyme as well. Additionally, I am using Redux.
If you see the jest test below you will see two simulated clicks. The reason for two simulated clicks in my test below is that the inputs inside the "showAddItem" check are both hidden. Clicking on "div#add_item" allows this div to be visible. After this, clicking on the "input#add_item_submit" should call the addItemToWatchList.
In my testing using a debugger and running the tests, the first simulated click does set the state to showAddItem to true. This allows the input "add_item_submit" to be available in the DOM. After that is shown the second simulated click in the test doesn't seem to fire because the addItemToWatchList doesn't get called. The output of my test is:
Expected number of calls: >= 1
Received number of calls: 0
relevant react component code:
{ this.state.showAddItem ? <div>
<input type="text" name="watchListItem" value={this.state.watchListItem}
onChange={this.handleInputChange}></input><br />
<input type="submit" id="add_item_submit" value="Add Item"
onClick={ () => this.addItemToWatchList()}/>
</div> : null }
relevant Jest/enzyme test
it('should call addItemToWatchList when adding a item', () => {
const initialState = { userReducer: {user: { id: 1, email: "test#test.com"}} }
const mockStore = configureStore([]);
const store = mockStore(initialState);
const component = mount(<Provider store={store}>
<AddItem />
</Provider>);
const mockedAddItemToWatchList = jest.fn();
component.instance().addItemToWatchList = mockedAddItemToWatchList
//sets state to showAddItem to true
component.find('div#add_item').simulate('click')
component.find('input#add_item_submit').simulate('click')
expect(mockedAddItemToWatchList).toHaveBeenCalled()
});
Just a side note, I also did try doing jest.spyOn without any luck as well.

The component.instance() might be returning the instance of Provided. You can try mounting AddItem directly an mocking its internal method behaviour.
const component = mount(
<AddItem />);

Related

Trying to test a simulate change on a textinput component using Jest

I'm trying to simulate a change event on a component(ListPage)
<TextInput
className="search-s"
id="textM"
width="m"
type="search"
handleChange={this.props.updateS}
placeholder="Search for a scenario"
/>
</div>
The handleChange attribute calls a prop function called updateS which looks like
updateS(e) {
this.setState({
name: e.target.value,
});
}
And what I currently have for the test function
it("should call handleChange on change with the correct params", () => {
const wrapper = shallow(<ListPage />);
const spy = jest.spyOn(wrapper.instance(), "handleChange");
wrapper.instance().forceUpdate();
const p = wrapper.find(".search-s");
p.simulate("change");
expect(spy).toHaveBeenCalled();
});
For some reason my test function doesnt work when I try to simulate a change and check whether the updateSearch function was called. All the guides online have examples of testing functions within the component but not passed props which I feel is what is causing the problem. Any insight would be great

Testing React Hooks side effects that depends on other side effects (or other tests)

I have a React Hook that has a simple input and button, and the button is disabled if there is no input, and that same button executes a fetch when it is enabled:
function MyComponent() {
const [ value, setValue ] = useState('')
function apiRequest() {
if (!value) {
return
}
axios.get('url')
.then(console.log)
.catch(console.log)
}
return (
<div>
<input onChange={e => setValue(e.target.value)} value={value} />
<button disabled={!value} onClick={apiRequest}>
Submit
</button>
</div>
)
}
I wrote two tests with Enzyme. The first one to test if the disabled prop is correct, and the second one to see if it actually fetches.
it('sets the disabled prop appropriately', function() {
const wrapper = mount(<MyComponent />)
const input = wrapper.find('input')
const btn = wrapper.find('button')
expect(btn.prop('disabled')).toBeTruthy()
input.simulate('change', 'abc123')
expect(btn.prop('disabled')).toBeFalsy()
})
it('fetches on submit', function () {
const wrapper = mount(<MyComponent />)
const input = wrapper.find('input')
const btn = wrapper.find('button')
input.simulate('change', 'abc123')
btn.simulate('click')
expect(axios.get).toHaveBeenCalled()
})
But unfortunately for the second test to work, the button needs to be enabled so text has to be inputted first. So in reality, the second test is also unintentionally testing the disabled prop as well because it will fail (the onClick will not fire) if the disabled prop isn't set correctly.
I followed React's recommended approach of
test React components without relying on their implementation details
which is react-testing-library's core principle, so I'm purely testing side effects. I'm using enzyme instead of that because my team is currently using Enzyme
How would I be able to rewrite my second test so I can only test for the fetch? Thanks in advance.
Edit: Or rather, a couple ways to rewrite this to properly test the fetch?
One thing you could do is replace the <div> with a <form> and add the onSubmit={e => apiRequest(value)} to it so the button can remain disabled and you can still move forward with your tests without introducing unnecessary external factors.
Also, move your function apiRequest() {...} outside of the component. It can take value as an argument instead of relying on the surrounding scope.
// You could even export this separately and make a test exclusively for this
// without also having to test the form itself
function apiRequest ( value ) {
if (!value) {
return
}
axios.get('url')
.then(console.log)
.catch(console.log)
}
function MyComponent() {
const [ value, setValue ] = useState('')
return (
<form onSubmit={e => { e.preventDefault(); apiRequest(value); }}>
<input onChange={e => setValue(e.target.value)} value={value} />
<button disabled={!value}>
Submit
</button>
</form>
)
}
To my believe you definitely are testing behavior, not implementation details.
It would be relying on implementation details if you say were setState()(sure it does not work to functional component that's why it's a bad pattern).
Using RTL you still had to change input prior clicking button, no differences here.
The only thing is rather implementation details it's relying on axios itself. You may use nock to handle any request made with any library. But I'm not sure if that worth it.

How do I test a method defined within a functional component, that interacts with DOM elements and has no arguments

I have been having trouble getting 100% test coverage on one of my buttons (A React functional components.) Basically when it is clicked, it executes some code and then also calls another method from within this onClick called resetButtons. This method will find all the buttons like it in the app and remove a class. This is a preemptive behavior so that only one button at a time can be active.
So far I have tested the click using .simulate, passing in a mocked domElement. And then test that the domElement.classList.add method is called with 'active'.
Obviously this being a DOM centered operation, I am finding it very difficult to test the resetButtons method that lies within the component. especially considering it doesn't have any methods.
I have tried defining the resetButtons method outside of the component and then exported it so the jest test could import it. However I have been unable to test the method as it seems to want it to be a spy or mock, and not the method itself. (Matcher error: received value must be a mock or spy function
)
Here is the react Functional Component:
import React from 'react';
import PropTypes from 'prop-types';
import classes from './MainButton.module.scss';
const MainButton = (props) => {
const resetButtons = () => {
const elements = document.getElementsByClassName('mainButton');
for (let i = 0; i < elements.length; i += 1) {
elements[i].classList.remove('active');
}
};
const handleClick = (event) => {
if (!event.target.classList.contains('active')) {
resetButtons();
event.target.classList.add('active');
props.setVisualState(props.className.split('-')[0]);
}
};
return (
<button
onClick={handleClick}
type="button"
className={`${classes.mainButton} ${props.className}`}
>
{props.children}
</button>
);
};
MainButton.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
setVisualState: PropTypes.func.isRequired,
};
MainButton.defaultProps = {
children: 'Button',
className: '',
};
export default MainButton;
Here is the Test
import React from 'react';
import { shallow } from 'enzyme';
import MainButton from './MainButton';
describe('MainButton', () => {
const domElement = { classList: { contains: jest.fn(), remove: jest.fn(), add: jest.fn() } };
const setVisualStateMock = jest.fn();
const mainButton = shallow(<MainButton setVisualState={setVisualStateMock} />);
it(' is rendered properly', () => {
expect(mainButton).toMatchSnapshot();
});
describe('when clicked', () => {
beforeEach(() => {
mainButton.find('button').simulate('click', { target: domElement });
});
it('it runs `classlist.add` to assign `active` class', () => {
expect(domElement.classList.add).toHaveBeenCalledWith('active');
});
it('it runs set visual state to update `Allergen` container `state`', () => {
expect(setVisualStateMock).toHaveBeenCalled();
});
});
});
Currently the coverage report is reporting 92% coverage, but the branch is at 50 and the line that is causing the trouble is on line 9 (the elements[i].classList.remove('active'); line.
I know at 90% I should probably just move on but this is something I want to be able to figure out. Feel like getting head around this will make me a better tested.
Hope you guys can help!
Fumbling around in the DOM yourself is an anti-pattern. That's React's job. Instead of manipulating the dom with target.classList.add you should have a state property that holds the status which of your inputs is currently active. Then, while rendering you can say className={isActiveInput ? "active": null}.
Because the state is not specific to your MainButton component you would lift the state up. If you have the state somewhere in the parent you don't have to crudely search for DOM elements by classname and manipulate the dom yourself.
Simply put, the rule of React is: You define how things are supposed to look like, React takes care that your definition becomes reality in the dom. If you manipulate the DOM yourself - you're doing it wrong.
When all of this is done, you will have no problem at all with tests, because all you have to do is provide the proper state and props, which is easy, and check that your callback is triggered onClick.
EDIT: Advanced version would be to use Context, but I'd go with state lifting first.
You should be able to mount multiple MainButtons, click one and expect that the other(s) had domElement.classList.remove called on them.
However, user konqi is right in that React provides better ways of manipulating elements/components.
You could replace this test:
expect(domElement.classList.add).toHaveBeenCalledWith('active');
with a test that checks that the button has (or does not have) the active className (instead of checking that the function was called with the right argument). With that test in place, if you like, you could refactor this in the way that konqi suggests.

Test that React prop method has been called with Jest

I have an Input component, which accepts a prop method and calls it when the user types something in. Code itself works as expected, but for some reasons, test fails. It thinks that prop method wasn't called. Why is it happening? For testing purposes, I use Jest and react-testing-library.
And second question. In real application, my idea is to test parameters that were passed to that prop method. Is it considered to be an implementation testing (I know that I should test it)?
Input.js
export default function Input({ onChange }) {
return <input onChange={onChange} />;
}
Test
import React from "react";
import { render, act, cleanup, fireEvent } from "react-testing-library";
import Input from "./input";
describe("Input tests", () => {
afterEach(cleanup);
it("Should call prop function", () => {
const onChange = jest.fn();
const { getByTestId } = render(<Input onChange={onChange} />);
const input = getByTestId("input");
act(() => {
fireEvent.change(input, { target: { value: "Q" } });
});
expect(onChange).toHaveBeenCalled();
});
});
https://codesandbox.io/s/y229669nvx
After reading this, it looks like it's by design to not assert against events handlers. Although it appears to work in React 16.5, however, using 16.8.x fails. I'd suggest moving to enzyme if you want to test such features.
Testing with react-testing-library fails (however, as you'll notice, when running the test, the input's value will actually change): https://codesandbox.io/s/n3rvy891n4
Testing with enzyme succeeds: https://codesandbox.io/s/lx34ny41nl
The reason why your test doesn't work is that you're using getByTestId to find your element. getByTestId looks for a DOM node that has a data-testid attribute.
In order to make your test pass, you have various options.
You could add a data-testid to your input: <input data-testid="input" onChange={onChange} />. This would work, however, it's better to avoid test ids whenever you can.
In a real application, your input would be rendered with a label, we can take advantage of that:
const { getByLabelText } = render(
<label>
My input
<Input onChange={onChange} />
</label>
)
const input = getByLabelText('My input')
Another solution is to use container which is one one of the values returned by render. It's a DOM node—like everything else in RTL—so you can use the usual DOM APIs:
const { container } = render(<Input onChange={onChange} />)
// Any of these would work
const input = container.firstChild
const input = container.querySelector('input')
As a side note, I agree that RTL tests seem more complicated if compared to Enzyme. There's a good reason for it. RTL pushes you to test your application as if it were a black box. This is a bit harder to do in the beginning but ultimately leads to better tests.
Enzyme, on the other hand, mocks most things by default and allows you to interact with your components implementation. This, in my experience, looks easier in the beginning but will produce brittle tests.
I encourage you to join the spectrum channel if you need help getting started.

Why is my button undefined in my test?

My test:
describe('button component', () => {
it('should toggle off when clicked', () => {
let component;
component = ReactTestUtils.renderIntoDocument(<Search />);
let searchbtn = ReactTestUtils.findRenderedDOMComponentWithTag(component, 'button');
ReactTestUtils.Simulate.click(searchbtn);
console.log(searchbtn, 'search button***'); //UNDEFINED
expect(searchbtn.calledOnce).to.equal(false);
})
});
This is my search component:
render() {
return (
<div className="search">
<button className="searchButton" onClick={this.handleSearch}>{this.state.on ? 'ON' : 'OFF'}</button>
</div>
);
}
Do I need to spy on it or mock it? or is there a better way to test buttons in react?
Since you have added enzyme tag I will answer using enzyme.
It can be tested very easily via shallow rendering -
const searchWrapper = shallow(<Search />);
const button = searchWrapper.find('button').first();
When you simulate click event on button the onClick handler provided via onClick prop which is handleSearch in your case will be called.
So if you are setting some state based on the onClick function call corresponding ui changes based on the state changes can be compared or checked if changes were reflecting correctly in the dom.
or
if you just want to check if method was called or not by using a fake method of similar name -
const onButtonClick = sinon.spy();
expect(onButtonClick.calledOnce).to.equal(false);
button.setProps({ onClick:onButtonClick});
button.simulate('click');
expect(onButtonClick.calledOnce).to.equal(true);

Resources