_react2.fireEvent.change is not a function | react testing library - reactjs

I try to simulate onChange with fireEvent.change, but get error _react2.fireEvent.change is not a function.
I checked official docs of react testing library, did everything exactly how it's there https://testing-library.com/docs/example-input-event, even created another test where copied all code from docs, and created exactly the same input in react component. Still the same - _react2.fireEvent.change is not a function.
My input
<input
data-testid='input-file'
value={img}
onChange={upImage}
className="file-upload"
type="file"
/>
my Test
it('img preview', () => {
const form = render(<Form addItem={func} />)
const input = form.getByTestId('input-file')
const file = new File(['dummy content'], 'example.png', {type: 'image/png'})
fireEvent.change(input, { target: { value: { file } } })
})
also, when I check input in test, in console it's showed like this:
Received: <input class="file-upload" data-testid="input-file" type="file" value="" />
there is simply not onChange

Related

Crash in Test React Jest when testing an input with onFocus to change an attibute

My Component have a field like this:
<Form.Label>E-mail</Form.Label>
<Form.Control
data-testid="inputemail"
type="email"
onFocus={() => setAttribute(false)}
readOnly={attribute}
placeholder="Enter e-mail"
/>
</Form.Group>
And I tried to pass this test:
it("should change 'Email' input value", async () => {
render(<LoginPage />);
const textVariable = "";
const inputemail = screen.getByTestId("inputemail");
inputemail.focus();
fireEvent.input(inputemail, { target: { value: textVariable } });
expect(inputemail).toHaveFocus(); //passing now
await waitFor(() => {
expect(inputemail.innerHTML).toBe(textVariable);
});
});
Test passed ok, but I get this warning:
What can I do? What is the issue, I don't understand.
The act error is saying that you are trying to access the component when it is not in stable state. Your onFocus is changing internal state of the component so the test needs to wait until it is rerendered.
Try to do the following, replace this:
inputemail.focus();
fireEvent.input(inputemail, { target: { value: textVariable } });
expect(inputemail).toHaveFocus(); //passing now
with this:
inputemail.focus();
await waitFor(() => expect(inputemail).toHaveFocus());
fireEvent.input(inputemail, { target: { value: textVariable } });
or instead of using fireEvent use the #testing-library/user-event library which should focus the field when using the type method.
await userEvent.type(inputemail, textVariable);
Just remember that user event lib API is async.

Input changes do not occur with default value with #testing-library/react

I have the following input:
<input
name="name"
type="text"
data-testid="input"
onChange={(e) => setTypedName(e.target.value)}
value=""
/>
the test:
test.only('Should change the value of the input ', async () => {
makeSut()
const nameInput = sut.getByTestId('input') as HTMLInputElement
fireEvent.change(nameInput, { target: { value: 'value' } })
expect(nameInput.value).toBe('value')
})
My assertion fails, as the change does not take effect, while the value remains to be ""
If I remove value="" from the input, change takes effect.
I have tried using fireEvent.input fireEvent.change, userEvent.type and nothing works.
It seems that when I use a default value the testing library does not accept changes, even though it works on production...
Any hints?
Using:
jest 27.3.1
#testing-library/react 12.1.2
#testing-library/user-event 13.5.0
I'm not sure, but perhaps this is due to React relying more on explicitly setting the value of components through JS rather than through "vanilla" HTML.
Explicitly setting the input value through JS makes your test pass:
import { render, screen } from "#testing-library/react";
import React, { useState } from "react";
import userEvent from "#testing-library/user-event";
const Component = () => {
const [value, setValue] = useState("");
return (
<input
name="name"
type="text"
data-testid="input"
onChange={(e) => setValue(e.target.value)}
value={value}
/>
);
};
test.only("Should change the value of the input ", async () => {
render(<Component />);
const nameInput = screen.getByTestId("input") as HTMLInputElement;
userEvent.type(nameInput, "value");
expect(nameInput.value).toBe("value");
});
PS I slightly modified your test to use render because I'm not sure what makeSut is, and I assume it's some custom function that you created to render your component.

Testing react controlled input with cypress

I'm use ant desing like component library and cypress for testing
I have this input and want to test it:
<Input
data-cy="methodology-input"
value={fileName}
onChange={(e) => setFileName(e.target.value)}
/>
But then I use cy.type() it not typed. I find the solution, but I think it bad:
cy.get('[data-cy=methodology-input]').then((input) => {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
const inputDOM = input[0];
nativeInputValueSetter.call(inputDOM, 'test');
inputDOM.dispatchEvent(new Event('change', { bubbles: true }));
});
How can I test it without bad code?

Best way to test input value in dom-testing-library or react-testing-library

What is the best way to test the value of an <input> element in dom-testing-library/react-testing-library?
The approach I've taken is to fetch the raw input element itself via the closest() method, which then gives me direct access to the value attribute:
const input = getByLabelText("Some Label")
expect(input.closest("input").value).toEqual("Some Value")
I was hoping that there was a way I could this without having to directly access HTML attributes. It didn't seem like it was in the spirit of the testing library. Perhaps something like the jest-dom toHaveTextContent matcher matcher:
const input = getByLabelText("Some Label")
expect(input).toHaveTextContent("Some Value")
UPDATE
Based on request in the comments, here is a code example showing a situation where I felt the need to test the value in the input box.
This is a simplified version of a modal component I built in my app. Like, extremely simplified. The whole idea here is that the modal opens up with the input pre-filled with some text, based on a string prop. The user can freely edit this input and submit it by pressing a button. But, if the user closes the modal and then reopens it, I would like to have the text reset to that original string prop. I wrote a test for it because a previous version of the modal DID NOT reset the input value.
I'm writing this in TypeScript so that the types of each prop are very clear.
interface Props {
onClose: () => void
isOpen: boolean
initialValue: string
}
export default function MyModal({ onClose, isOpen, initialValue }) {
const [inputValue, setInputValue] = useState(initialValue)
// useEffect does the reset!
useEffect(() => {
if (!isOpen) {
setNameInput(initialValue)
}
}, [isOpen, initialValue])
return (
<SomeExternalLibraryModal isOpen={isOpen} onClose={onClose}>
<form>
<input
value={inputValue}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setInputValue(e.target.value)
}
/>
<button onClick={onClose}>Cancel</button>
</form>
</SomeExternalLibraryModal>
)
}
You are right in being suspicious of your testing method in regards to how this testing library wants you to test. The simplest answer to this question would be to use the getByDisplayValue query. It will search for an input, textarea, or select that has the value you are attempting to find. For example, using your component as an example, if I was trying to verify that inputValue = 'test', I would search like
expect(screen.getByDisplayValue('test')).toBeInTheDocument();
That is all you need to do. I assume your test is only rendering the MyModal component. Even if you have multiple inputs, it doesn't matter in regards to testing philosophy. As long as the getByDisplayValue finds any input with that value, it is a successful test. If you have multiple inputs and want to test that the exact input has the value, you could then dig into the element to determine it is the correct input:
note: you will need jest-dom for this to work.
expect(screen.getByDisplayValue('test')).toHaveAttribute('id', 'the-id');
or (without jest-dom):
expect(screen.getByDisplayValue('test').id).toBe('the-id');
You can of course search for any attribute you like.
One final alternative for testing the value is to find the input by role. This won't work in your example's case unless you add a label and affiliate it to your input through the htmlFor attribute. You could then test it like such:
expect(screen.getByRole('input', { name: 'the-inputs-id' })).toHaveValue('test');
or (without jest-dom):
expect(screen.getByRole('input', { name: 'the-inputs-id' }).value).toBe('test');
This I believe is the best way to test for the value while making sure the correct input has the value. I would suggest the getByRole method, but again, you will need to add a label to your example.
You can use screen.getByDisplayValue() to get the input element with a displayed value and compare it to your element.
type TestElement = Document | Element | Window | Node
function hasInputValue(e: TestElement, inputValue: string) {
return screen.getByDisplayValue(inputValue) === e
}
In your test:
const input = screen.getByLabelText("Some Label")
fireEvent.change(input, { target: { value: '123' } })
expect(hasInputValue(input, "123")).toBe(true)
expect(screen.getByLabelText("Name")).toHaveValue("hello"); - this gets you the value for the input :)
<label class="label" for="name">
Name
</label>
<div class="control ">
<input
class="input"
for="name"
id="name"
name="name"
value="hello"
/>
</div>
Test:
userEvent.type(screen.getByLabelText("Name"), "hello")
await waitFor(() => {
expect(screen.getByLabelText("Name")).toHaveValue("hello");
});
Using #testing-library/dom (or any of the wrapped libraries here)
You can do:
expect(inputField).toHaveDisplayValue('some input value');
Full example:
test('should show input with initial value set', async () => {
render(<Input type="text" value="John Doe" data-testid="form-field-firstname" />);
const inputField = await screen.findByTestId(`form-field-firstname`);
await waitFor(() => expect(inputField).toHaveDisplayValue('John Doe')));
});
There is very clean way to test it using testing library.
//In describe
const renderComponent = (searchInputValue, handleSearchInputValue) => {
const wrapper = render(<yourComponentWithInput
value={searchInputValue}
onChange={handleSearchInputValue}
/>);
return wrapper;
};
//In test
const mockHandleSearchInputValue = jest.fn();
const { getByLabelText } = renderComponent('g', mockHandleSearchInputValue);
const inputNode = getByLabelText('Search label'); // your input label
expect(inputNode.value).toBe('s'); // to test input value
fireEvent.change(inputNode, { target: { value: 'su' } }); // triggers onChange event
expect(mockHandleSearchInputValue).toBeCalledWith('su'); // tests if onChange handler is called with proper value

How to test props that are updated by an onChange handler in react testing library?

I've got an onChange handler on an input that I'm trying to test based on what I've read in the Dom Testing Library docs here and here.
One difference in my code is that rather than using local state to control the input value, I'm using props. So the onChange function is actually calling another function (also received via props), which updates the state which has been "lifted up" to another component. Ultimately, the value for the input is received as a prop by the component and the input value is updated.
I'm mocking the props and trying to do a few simple tests to prove that the onChange handler is working as expected.
I expect that the function being called in the change handler to be called the same number of times that fireEvent.change is used in the test, and this works with:
const { input } = setup();
fireEvent.change(input, { target: { value: "" } });
expect(handleInstantSearchInputChange).toHaveBeenCalledTimes(1);
I expect that the input.value is read from the original mock prop setup, and this works with:
const { input } = setup();
expect(input.value).toBe("bacon");
However, I'm doing something stupid (not understanding mock functions at all, it would seem), and I can't figure out why the following block does not update the input.value, and continues to read the input.value setup from the original mock prop setup.
This fails with expecting "" / received "bacon" <= set in original prop
fireEvent.change(input, { target: { value: "" } });
expect(input.value).toBe("");
QUESTION: How can I write a test to prove that the input.value has been changed given the code below? I assume that I need the mock handleInstantSearchInputChange function to do something, but I don't really know what I'm doing here quite yet.
Thanks for any advice on how to do and/or better understand this.
Test File
import React from "react";
import InstantSearchForm from "../../components/InstantSearchForm";
import { render, cleanup, fireEvent } from "react-testing-library";
afterEach(cleanup);
let handleInstantSearchInputChange, props;
handleInstantSearchInputChange = jest.fn();
props = {
foodSearch: "bacon",
handleInstantSearchInputChange: handleInstantSearchInputChange
};
const setup = () => {
const utils = render(<InstantSearchForm {...props} />);
const input = utils.getByLabelText("food-search-input");
return {
input,
...utils
};
};
it("should render InstantSearchForm correctly with provided foodSearch prop", () => {
const { input } = setup();
expect(input.value).toBe("bacon");
});
it("should handle change", () => {
const { input } = setup();
fireEvent.change(input, { target: { value: "" } });
expect(input.value).toBe("");
fireEvent.change(input, { target: { value: "snickerdoodle" } });
expect(input.value).toBe("snickerdoodle");
});
Component
import React from "react";
import PropTypes from "prop-types";
const InstantSearchForm = props => {
const handleChange = e => {
props.handleInstantSearchInputChange(e.target.value);
};
return (
<div className="form-group">
<label className="col-form-label col-form-label-lg" htmlFor="food-search">
What did you eat, fatty?
</label>
<input
aria-label="food-search-input"
className="form-control form-control-lg"
onChange={handleChange}
placeholder="e.g. i ate bacon and eggs for breakfast with a glass of whole milk."
type="text"
value={props.foodSearch}
/>
</div>
);
};
InstantSearchForm.propTypes = {
foodSearch: PropTypes.string.isRequired,
handleInstantSearchInputChange: PropTypes.func.isRequired
};
export default InstantSearchForm;
The way you are thinking about your tests is slightly incorrect. The behavior of this component is purely the following:
When passed a text as a prop foodSearch renders it correctly.
Component calls the appropriate handler on change.
So only test for the above.
What happens to the foodSearch prop after the change event is triggered is not the responsibility of this component(InstantSearchForm). That responsibility lies with the method that handles that state. So, you would want to test that handler method specifically as a separate test.

Resources