Jest/Enzyme Shallow testing RFC - not firing jest.fn() - reactjs

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.

Related

How to fire and test a real paste event (not simulated by calling the prop) in Jest and Enzyme

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("");
});
});

Is there a way to pass React state into WebSocket.onmessage?

In TypeScript I am trying to read the value of a stateful variable (created with React.useState) from within a WebSocket's onmessage function. The value is only ever read as what it was at the time of onmessage definition (the initial state, {state: 1}).
Foo is created like a React component (<Foo />). Bar is called only once, but the state (myState) can be updated many times. Why isn't WebSocket.onmessage() using the reference passed to it?
I tried changing the type of propState from MyStatefulObject to &MyStatefulObject and executing the Bar function code directly, but it didn't work and Bar is actually quite large, so that is not ideal.
import { useEffect, useState } from 'react';
interface MyStatefulObject {state: number}
interface BarProps {propState: &MyStatefulObject}
const Foo = () => {
const [myState, setMyState] = useState<MyStatefulObject>({state: 1}),
[wsInitialized, setWSInitialized] = useState<boolean>(false);;
useEffect(() => {
if (!wsInitialized) {
Bar({propState: myState});
setWSInitialized(true);
}
})
return (
<TextField
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setMyState({state: +(e.target.value)});
console.log(myState); // prints user input, confirming state change
}}
/>
)
}
const Bar = (p: BarProps) => {
const ws = WebSocket('url');
ws.onopen = () => {
ws.send(/* Initialization Message */);
};
ws.onmessage = (e: MessageEvent) => {
console.log(p.propState.state); // always prints 1
};
}

React Number Format unit test with isAllowed

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
});

Use toHaveBeenCalledWith to check EventTarget

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"
})
})
);

Test useRef onError Fn, with React-Testing-Library and Jest

I have this simple fallbackImage Component:
export interface ImageProps {
srcImage: string;
classNames?: string;
fallbackImage?: FallbackImages;
}
const Image = ({
srcImage,
classNames,
fallbackImage = FallbackImages.FALLBACK
}: ImageProps) => {
const imgToSourceFrom = srcImage;
const imgToFallbackTo = fallbackImage;
const imageRef = useRef(null);
const whenImageIsMissing = () => {
imageRef.current.src = imgToFallbackTo;
imageRef.current.onerror = () => {};
};
return (
<img ref={imageRef} src={imgToSourceFrom} className={classNames} onError={whenImageIsMissing} />
);
};
export default Image;
It works perfectly. I have test running for it with Jest and React-Testing-Library. I have tested all but one scenario. This one:
const whenImageIsMissing = () => {
imageRef.current.src = imgToFallbackTo;
imageRef.current.onerror = () => {}; // This line.
};
This line basically prevents an infinite Loop in case both images are missing
The Problem:
I want to test that my onerror function has been called exactly one time. Which I am really stuck on how to do it. Here is the test...
const { container } = render(<Image srcImage={undefined} fallbackImage={undefined} />);
const assertion = container.querySelector('img').onerror;
fireEvent.error(container.firstElementChild);
console.log(container.firstElementChild);
expect(container.firstElementChild.ref.current.onerror).toHaveBeenCalledTimes(1);
// This though has no reference to a real value. Is an example of what I want to get at.
The Question:
How to access the ref callback function and check how many times has my function been called?
Any ideas on this. I am at a loss, I tried mocking refs, I tried mocking and spying on the component. I tried using act and async/await, in case it was called after. I really need some help on this..
You should check if your function is called or not, that's called testing implementation details, rather you should check if your img element have correct src.
Even you should add some alt and user getByAltText to select image element
const { getByAltText } = render(<Image srcImage={undefined} fallbackImage={undefined} />);
const imageElement = getByAltText('Image Alt');
fireEvent.error(imageElement);
expect(imageElement.src).toEqual(imgToFallbackTo);
You have 2 options:
Add a callback to your props that will be called when whenImageIsMissing is called:
export interface ImageProps {
srcImage: string;
classNames?: string;
fallbackImage?: FallbackImages;
onImageMissing?:();
}
const Image = ({
srcImage,
classNames,
onImageMissing,
fallbackImage = FallbackImages.FALLBACK
}: ImageProps) => {
const imgToSourceFrom = srcImage;
const imgToFallbackTo = fallbackImage;
const imageRef = useRef(null);
const whenImageIsMissing = () => {
imageRef.current.src = imgToFallbackTo;
imageRef.current.onerror = () => {};
if (onImageMissing) onImageMissing();
};
return (
<img ref={imageRef} src={imgToSourceFrom} className={classNames} onError={whenImageIsMissing} />
);
};
and then insert jest.fn in your test and check how many times it was called.
The other option is to take the implementation of whenImageIsMissing and put it inside image.util file and then use jest.spy to get number of calls. Since you are using a function component there is no way to access this function directly.
Hope this helps.

Resources