I am using the react number format for inputting the number input fields.
<TargetRow>
<NumberFormat
type="text"
isNumericString={true}
decimalScale={2}
name="otbTarget"
value={otbTarget === null ? '' : otbTarget}
className="TargetRow__input TargetRow__otbTarget"
onBlur={this.updateTarget}
onChange={this.handleChangeOTBTarget}
isAllowed={this.limitInputValue}
/>
/>
function for the isAllowed, is to limit the length of values entered in the field.
limitInputValue = ({ value }: NumberFormatValues ) => value.length <= ALLOWED_INPUT_LENGTH;
I need to write a unit test for this functionality, to check if the values entered in the field with NumberFormat gets restricted by using the isAllowed method.
I tried using the enzyme simulate for change, but as it turns out it calls the handleChangeOTBTarget method, which is as expected.
I am not able to understand how the isAllowed will be triggered.
Unit test which I am writing is :
it('should restrict values with ALLOWED_INPUT_LENGTH', async () => {
// ALLOWED_INPUT_LENGTH = 3
const mockTarget = 1234512345;
component = shallow(<TargetRow {...defaultProps} otbTarget={mockTarget} />);
const targetInput = component.find('.TargetRow__otbTarget');
targetInput.simulate('change', { target: { value: mockTarget } });
targetInput.simulate('blur');
expect(targetInput.props().value).toBe( 123 );
});
As you can see there is way to simulate onchange, which is working as stated above. But I am not able to trigger the method which is on isAllowed
I would do something as the following:
it('should restrict values with ALLOWED_INPUT_LENGTH', async () => {
const isValid
// ALLOWED_INPUT_LENGTH = 3
const mockTarget = 1234512345;
component = shallow(<TargetRow {...defaultProps} otbTarget={mockTarget} />);
component.instance().limitInputValue = jest.fn(); // MOCK THE METHOD WE ADD TO isAllowed prop
const targetInput = component.find('.TargetRow__otbTarget');
targetInput.simulate('change', { target: { value: mockTarget } });
targetInput.simulate('blur');
expect(targetInput.props().value).toBe( 123 );
expect(component.instance().limitInputValue).toHaveBeenCalled() // Check if the method has been called at all
});
Related
I'm trying to unit test a very simple feature in a React app where I'm blocking the user from pasting into a textarea by adding an event.preventDefault() in the event handler, like so:
function handlePaste(event) {
event.preventDefault();
}
// ... pass it down as props
<TextareaComponent onPaste={handlePaste} />
The problem I'm having is that every method I've found of dispatching events in Jest or Enzyme just "simulates" the event by getting the function passed to the onPaste prop and calling it directly with a mock event object. That's not what I'm interested in testing.
Ideally I want to do something like this, testing that the actual value of the input hasn't changed after pasting:
const wrapper = mount(<ParentComponent inputValue="Prefilled text" />);
const input = wrapper.find(TextareaComponent);
expect(input.value).toEqual("Prefilled text")
input.doAPaste("Pasted text")
expect(input.value).not.toEqual("Pasted text")
expect(input.value).toEqual("Prefilled text")
But haven't been able to find a method that works. Any help would be appreciated!
Since you're just testing against a synthetic event (and not some sort of secondary action -- like a pop up that warns the user that pasting is disabled), then the easiest and correct solution is to simulate a paste event, pass it a mocked preventDefault function, and then assert that the mocked function was called.
Attempting to make assertions against a real paste event is pointless as this a React/Javascript implementation (for example, making assertions that a callback function is called when an onPaste/onChange event is triggered). Instead, you'll want to test against what happens as a result of calling the callback function (in this example, making assertions that event.preventDefault was called -- if it wasn't called, then we know the callback function was never executed!).
Working example (click the Tests tab to run the assertions):
To keep it simple, I'm just asserting that the input is initially empty and then only updates the value if an onChange event was triggered. This can very easily be adapted to have some sort of passed in prop influence the default input's value.
App.js
import React, { useCallback, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
const handleChange = useCallback(
({ target: { value } }) => setValue(value),
[]
);
const handlePaste = useCallback((e) => {
e.preventDefault();
}, []);
const resetValue = useCallback(() => {
setValue("");
}, []);
const handleSubmit = useCallback(
(e) => {
e.preventDefault();
console.log(`Submitted value: ${value}`);
setValue("");
},
[value]
);
return (
<form onSubmit={handleSubmit}>
<label htmlFor="foo">
<input
id="foo"
type="text"
data-testid="test-input"
value={value}
onPaste={handlePaste}
onChange={handleChange}
/>
</label>
<br />
<button data-testid="reset-button" type="button" onClick={resetValue}>
Reset
</button>
<button type="submit">Submit</button>
</form>
);
};
export default App;
App.test.js
import React from "react";
import { configure, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App from "./App";
configure({ adapter: new Adapter() });
const value = "Hello";
describe("App", () => {
let wrapper;
let inputNode;
beforeEach(() => {
wrapper = mount(<App />);
// finding the input node by a 'data-testid'; this is not required, but easier
// when working with multiple form elements and can be easily removed
// when the app is compiled for production
inputNode = () => wrapper.find("[data-testid='test-input']");
});
it("initially displays an empty input", () => {
expect(inputNode()).toHaveLength(1);
expect(inputNode().props().value).toEqual("");
});
it("updates the input's value", () => {
inputNode().simulate("change", { target: { value } });
expect(inputNode().props().value).toEqual(value);
});
it("prevents the input's value from updating from a paste event", () => {
const mockPreventDefault = jest.fn();
const prefilledText = "Goodbye";
// updating input with prefilled text
inputNode().simulate("change", { target: { value: prefilledText } });
// simulating a paste event with a mocked preventDefault
// the target.value isn't required, but included for illustration purposes
inputNode().simulate("paste", {
preventDefault: mockPreventDefault,
target: { value }
});
// asserting that "event.preventDefault" was called
expect(mockPreventDefault).toHaveBeenCalled();
// asserting that the input's value wasn't changed
expect(inputNode().props().value).toEqual(prefilledText);
});
it("resets the input's value", () => {
inputNode().simulate("change", { target: { value } });
wrapper.find("[data-testid='reset-button']").simulate("click");
expect(inputNode().props().value).toEqual("");
});
it("submits the input's value", () => {
inputNode().simulate("change", { target: { value } });
wrapper.find("form").simulate("submit");
expect(inputNode().props().value).toEqual("");
});
});
I've built a custom Input React component (think wrapper) that renders an HTML input element. When a value is entered the change is passed to the parent component like this:
const handleChange = (event: SyntheticInputEvent<EventTarget>) => {
setCurrentValue(event.target.value);
props.onChange(event);
};
Everything works as expected but now I want to write a test for it:
it('Should render a Text Input', () => {
const onChange = jest.fn();
const { queryByTestId } = renderDom(
<Input type={InputTypesEnum.TEXT} name='car' onChange={onChange} />
);
const textInput = queryByTestId('text-input');
expect(textInput).toBeTruthy();
const event = fireEvent.change(textInput, { target: { value: 'Ford' }});
expect(onChange).toHaveBeenCalledTimes(1);
});
This works fine too except that I want to add a final expect using toHaveBeenCalledWith. I've tried several things but can't figure out how to do it. Any ideas?
Update: I've been reading this: https://reactjs.org/docs/events.html#event-pooling. It appears that if I change handleChange like this:
const handleChange = (event: SyntheticInputEvent<EventTarget>) => {
event.persist();
setCurrentValue(event.target.value);
props.onChange(event);
};
then the received object from onChange does change to include my test data. That said, I don't like the idea of altering an important feature of production code (in this case, event pooling) simply to accommodate a test.
You can do something like this with toHaveBeenCalledWith
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
target: expect.objectContaining({
value: "Ford"
})
})
);
I'm trying to test the onChange prop (and the value) of an input on an RFC. On the tests, trying to simulate the event doesn't fire the jest mock function.
The actual component is connected (with redux) but I'm exporting it also as an unconnected component so I can do a shallow unit test. I'm also using some react-spring hooks for animation.
I've also tried to mount instead of shallow the component but I still get the same problem.
MY Component
export const UnconnectedSearchInput: React.FC<INT.IInputProps> = ({ scrolled, getUserInputRequest }): JSX.Element => {
const [change, setChange] = useState<string>('')
const handleChange = (e: InputVal): void => {
setChange(e.target.value)
}
const handleKeyUp = (): void => {
getUserInputRequest(change)
}
return (
<animated.div
className="search-input"
data-test="component-search-input"
style={animateInputContainer}>
<animated.input
type="text"
name="search"
className="search-input__inp"
data-test="search-input"
style={animateInput}
onChange={handleChange}
onKeyUp={handleKeyUp}
value={change}
/>
</animated.div>
)
}
export default connect(null, { getUserInputRequest })(UnconnectedSearchInput);
My Tests
Here you can see the test that is failing. Commented out code is other things that I-ve tried so far without any luck.
describe('test input and dispatch action', () => {
let changeValueMock
let wrapper
const userInput = 'matrix'
beforeEach(() => {
changeValueMock = jest.fn()
const props = {
handleChange: changeValueMock
}
wrapper = shallow(<UnconnectedSearchInput {...props} />).dive()
// wrapper = mount(<UnconnectedSearchInput {...props} />)
})
test('should update input value', () => {
const input = findByTestAttr(wrapper, 'search-input').dive()
// const component = findByTestAttr(wrapper, 'search-input').last()
expect(input.name()).toBe('input')
expect(changeValueMock).not.toHaveBeenCalled()
input.props().onChange({ target: { value: userInput } }) // not geting called
// input.simulate('change', { target: { value: userInput } })
// used with mount
// act(() => {
// input.props().onChange({ target: { value: userInput } })
// })
// wrapper.update()
expect(changeValueMock).toBeCalledTimes(1)
// expect(input.prop('value')).toBe(userInput);
})
})
Test Error
Nothing too special here.
expect(jest.fn()).toBeCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
71 | // wrapper.update()
72 |
> 73 | expect(changeValueMock).toBeCalledTimes(1)
Any help would be greatly appreciated since it's been 2 days now and I cn't figure this out.
you don't have to interact with component internals; instead better use public interface: props and render result
test('should update input value', () => {
expect(findByTestAttr(wrapper, 'search-input').dive().props().value).toEqual('');
findByTestAttr(wrapper, 'search-input').dive().props().onChange({ target: {value: '_test_'} });
expect(findByTestAttr(wrapper, 'search-input').dive().props().value).toEqual('_test_');
}
See you don't need to check if some internal method has been called, what's its name or argument. If you get what you need - and you require to have <input> with some expected value - it does not matter how it happened.
But if function is passed from the outside(through props) you will definitely want to verify if it's called at some expected case
test('should call getUserInputRequest prop on keyUp event', () => {
const getUserInputRequest = jest.fn();
const mockedEvent = { target: { key: 'A' } };
const = wrapper = shallow(<UnconnectedSearchInput getUserInputRequest={getUserInputRequest } />).dive()
findByTestAttr(wrapper, 'search-input').dive().props().onKeyUp(mockedEvent)
expect(getUserInputRequest).toHaveBeenCalledTimes(1);
expect(getUserInputRequest).toHaveBeenCalledWith(mockedEvent);
}
[UPD] seems like caching selector in interm variable like
const input = findByTestAttr(wrapper, 'search-input').dive();
input.props().onChange({ target: {value: '_test_'} });
expect(input.props().value).toEqual('_test_');
does not pass since input refers to stale old object where value does not update.
At enzyme's github I've been answered that it's expected behavior:
This is intended behavior in enzyme v3 - see https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#calling-props-after-a-state-change.
So yes, exactly - everything must be re-found from the root if anything has changed.
I have some problems with testing events for nested components.
This is how my component tree look like:
- ModalComponent (Stateful with value for Input and update handler)
- - ModalType (stateless, passes value and update down to input)
- - - Input (stateless)
I have the value state and the handler for updating my value in my ModalComponent. This information just passed down through ModalType to my Input element via props.
I have tried to mount my ModalComponent with enzyme, find my input and simulate a change on the element. But this did not work.
What is the best strategy to test nested component when the handler and state are n parents components above?
EDIT
I have created a lean demo setup of my component in a separate blank react project
class App extends Component {
state = {
inputs: {
machineName: 'Empty'
}
}
onChangeHandler = (e) => {
let updatedState = null
console.log(e.target.value, e.target.id);
switch (e.target.id) {
case 'machineName':
updatedState = { ...this.state.inputs, machineName: e.target.value }
this.setState({inputs: updatedState})
break;
default:
break;
}
}
render() {
return (
<div className="App">
<ModalType onChange={this.onChangeHandler} value={this.state.inputs}></ModalType>
</div>
);
}
}
const ModalType = (props) => {
return <Input onChange={props.onChange} value={props.value.machineName}></Input>
}
const Input = (props) => (
<input id="machineName" onChange={props.onChange} value={props.value}></input>
)
My testing script
test('should handle change on input', () =>{
const wrapper = mount(<App/>)
wrapper.setState({inputs: { machineName: 'Empty' }})
wrapper.find('input').simulate('focus')
wrapper.find('input').simulate('change', {target: {value: '123'}})
wrapper.update()
// fails
expect(wrapper.state().inputs.machineName).toEqual('123')
// fails too
expect(wrapper.find('input').props().value).toEqual('123')
})
Thanks!
const wrapper = mount(<ModalComponent />);
const input = wrapper.find('input');
const event = {target: {value: 'testValue'}};
input.simulate('change', event);
The code above is a working example of how to simulate a change event on your input.
Edit
Your event is not correct. Since your handler is doing something only if the target id is machineName, you need to add that id to your mock event.
wrapper.find('input').simulate('change', {target: {value: '123', id: 'machineName'}})
I am trying this example :
https://github.com/reactjs/redux/tree/master/examples/todomvc
Based in this solution I have created a unit test that looks like this:
it('should call addTodo if length of text is greater than 0', () => {
const props = {
addTodo: jest.fn()
}
let cb = shallow(<Header {...props} />)
expect(props.addTodo).not.toBeCalled()
cb.find('TodoTextInput').simulate("onSave",{text:"fsdhsd"});
//error starts here:
expect(props.addTodo).toBeCalled()
});
The result of this one is :
FAIL src/components/NewHeaderTest.spec.js
●
Header enzyme style › should call addTodo if length of text is greater than 0
expect(jest.fn()).toBeCalled()
Expected mock function to have been called.
at Object.it (src/components/NewHeaderTest.spec.js:46:27)
This is part of the component:
handleSave = text => {
console.log('handlesave');
if (text.length > 0) {
this.props.addTodo(text);
}
}
render = () => {
return (
<header className="header">
<TodoTextInput onSave={this.handleSave}>
</TodoTextInput>
</header>)
}
How can I fix the unittest or how to pass in an argument for the simulate statement: cb.find('TodoTextInput').simulate("onSave",{text:"fsdhsd"});