How to test react component with refs? - reactjs

I have some react functional component:
export const Chat = props => {
..............
const messagesRef = useRef(null);
useEffect(() => messages && messagesRef.current.scrollTo(0, 99999), [messages]);
.............
return (
...........
<div className='chat-content__list' ref={messagesRef}>
</div>
............
)
}
I'm not good at testing and I just want to test the render of my component, the code is like this:
it('Render Messages chat', async () => {
const { container } = render(<Chat ...someProps />)
}
After running this test, I get this error: TypeError: messagesRef.current.scrollTo is not a function
How to work with refs during testing and how to fix the error in my case?

Jest React tests run in an abstraction of the browser called jsdom. Because it isn't a real browser not all functions are implemented. I suspect that the scrollTo is one such example.
For example, this answer suggests that scrollIntoView is also not implemented and to get around it you can provide a spy for it.
Jest supports having a setup file for setup code required by all tests. You can look into placing the mock there.

Related

How to test Component which requires hook using react testing library?

I have built a component which requires a methods and data supplied by custom hook
const MyComponent = () => {
const { data, methods } = useMyCustomHook({ defaultValues: defaultValues });
return <MyAnotherComponent data={data} label="Some Text" method={methods} />;
}
I am writing test using react testing library to test MyComponent or to be more specific to test MyAnotherComponent
Here is my testing code
test("Test MyAnotherComponent Label", () => {
const { data, methods } = useMyCustomHook({ defaultValues: defaultValues });
render(
<MyAnotherComponent
data={data}
label="Some Text"
method={methods}
/>
);
expect(screen.getByLabelText("Some Text")).toBeInTheDocument();
});
My testcase fails with an error saying Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons
I have looked up for solutions but some are too simple and some are too complex. Thanks in advance
The error is caused by the fact that you are calling the hook inside a function, not inside a React component.
Since you only want to test MyAnotherComponent, I don't see why you'd need to call the hook and not mock the data and methods directly to pass them to MyAnotherComponent because what you are currently doing is to re-write MyComponent.

react-test-renderer's create() vs. #testing-library/react's render()

I'm new to React and confused about all the testing libraries. I got my test code to work but it seems redundant to have to call create() from react-test-renderer in order to use its toMatchSnapshot() and have to call render() from #testing-library/react in order to use its assertions such as getByLabelText().
import {render} from '#testing-library/react';
import {act, create} from 'react-test-renderer';
it('renders a login screen', () => {
let mockInitialState: AppState = {
auth: initialAuthState
};
let component = <Root initialState={mockInitialState}/>;
let tree = null;
act(() => {
tree = create(component);
});
expect(tree).toMatchSnapshot();
const {getByLabelText, getByText} = render(component);
expect(getByLabelText(/Email Address.*/));
expect(getByLabelText(/Password*/));
expect(getByText('Sign in'));
});
As a newbie, it's hard for me to understand the difference between all these React libraries. But I'm thinking there must be a simpler way.
How can I simplify my test code so I only have to call one thing that renders the component so that I can do snapshot testing and more specific assertions?
I got the answer from Ziad Saab at Codementor.io:
create() allows you test against the virtual DOM (i.e. the "React DOM")
render() comes from react testing library and renders your tree but also allows you to have all the get*() assertions. It allows you to test against the DOM.
Here's how the code can be simplified:
it('renders a login screen', () => {
let mockInitialState: AppState = {
auth: initialAuthState
};
const {container, getByLabelText, getByText} = render(<Root initialState={mockInitialState}/>);
expect(container.firstChild).toMatchSnapshot();
expect(getByLabelText(/Email Address.*/));
expect(getByLabelText(/Password*/));
expect(getByText('Sign in'));
});
Ziad let me know that there was no reason to have act(), it was something to work around a bug in create(). Now that the code doesn't used create() there is no need for act().
As a result, my snapshot now contains class instead of className because class is what's in the actual HTML DOM whereas className is its equivalent in React's "Virtual DOM".
(Before) Snapshot with create() based on React's Virtual DOM:
className="MuiBox-root MuiBox-root-256"
(After) Snapshot with render() based on HTML DOM:
class="MuiBox-root MuiBox-root-256"
If you're using Create React App then I'd stick with react-testing-library since it comes with it.
Instead of container, you can also use asFragment for snapshot testing.
const {container} = render(<Root initialState={mockInitialState}/>);
expect(asFragment).toMatchSnapshot();

How to create an unit test for UncontrolledTooltip from reactstrap that does not handle state management directly?

I implemented simple UncontrolledTooltip from reactstrap. The doc (https://reactstrap.github.io/components/tooltips/) says
uncontrolled component can provide the functionality wanted without the need to manage/control the state of the component
If I want to implement an unit test (e.g. jest + enzyme) for testing its state as either open or close, how can I create a unit test without manually tinkering with state value? Is this possible to achieve it? It seems only possible with regular Tooltip component but I like to hear advice from seasoned engineers.
[Update]:
Upon request I include here tooltip and unit test I am trying to execute. At the moment, I want to simulate hover on the tooltip however mockHover.mock.calls.length returns as 0 which I interpret as mock function was not triggered.
Here is my Tooltip.
import React from 'react';
import { UncontrolledTooltip } from 'reactstrap';
export default class MyTooltip extends React.Component {
render() {
const { metaData, wg } = this.props;
return (
<div>
<UncontrolledTooltip placement="bottom" trigger={'hover'} target={wg}>
{metaData}
</UncontrolledTooltip>
</div>
);
}
}
Here is my unit test that use jest and enzyme:
describe('<MyTooltip />', () => {
it('Tooltip unit test', () => {
const mockHover = jest.fn();
const wrapper = shallow(<MyTooltip trigger={mockHover} />);
expect(wrapper.find(UncontrolledTooltip));
wrapper.find(UncontrolledTooltip).simulate('hover');
expect(mockHover.mock.calls.length).toEqual(1);
});
});
There are few important things to start from:
UncontrolledTooltip is part of 3rd party package so you won't test it explicitly.
Instead you better focus on testing your wrapper around UncontrolledTooltip.
simulate is nothing related to events browser's system. It's just a syntax sugar to do props().onHover(...). So if target component has such a prop - and it's a callback-function - it will be called. If there is no such a prop - it would be up to defaultProps what's going on. Anyway nothing like 'emulating mouse cursor over the element'.
shallow() will stop rendering at level of UncontrolledTooltip(its internals will not be rendered)
Keeping that in mind I see you able only:
your component finally renders UncontrolledTooltip with expected constant prop values
both metaData and wg props are passed down to UncontrolledTooltip
it('renders UncontrolledTooltips under the hood', () => {
const wg = '1';
const metaData = (<span>2</span>);
const wrapper = shallow(<MyTooltip wg={wg} metaData={metaData} />);
const innerTooltip = wrapper.find(UncontrolledTooltip);
/*
I don't validate `find(UncontrolledTooltip).toHaveLength(1)`
since assertion on `.find(..).props()` would throw exception otherwise
*/
expect(innerTooltip.props().placement).toEqual('bottom');
expect(innerTooltip.props().trigger).toEqual('hover');
expect(innerTooltip.props().wg).toEqual(wg);
expect(innerTooltip.props().metaData).toEqual(metaData);
});

Shallow Test of React Component With React Router V4

I am attempting to do a shallow test of my React Login Component with Jest + Enzyme, but am running into issues with React Router V4. When the test is run, I get an error from Jest claiming:
cannot read property push of undefined.
Here is my test, and below that, is the method inside my component that is being tested.
describe('Login Component', () => {
let wrapper
const mockSignInFunc = jest.fn()
beforeEach(() => {
wrapper = shallow(<Login signIn={mockSignInFunc} />)
})
it('renders itself', () => {
expect(wrapper.find('.login').length).toEqual(1)
})
it('should call the signIn function on submittal', () => {
wrapper.find('form').simulate('submit', { preventDefault() {}})
expect(mockSignInFunc.mock.calls[0][0]).toBe(1)
})
}) // end describe block
Method being tested:
handleSubmit = e => {
e.preventDefault()
this.props.signIn()
this.props.history.push('/') // fails here
}
If I remove the this.props.history.push('/') line of code, the test passes, so I know it is a React Router problem. I tried to read some other documentation on this, where wrapping your component in a MemoryRouter is supposed to work, but per usual, the React Router docs leave plenty to be desired.
I am actually using HashRouter in my app, and am not sure if using a MemoryRouter is just a hack to get it to work. Beats me.
Does anyone know how I can get this test to pass?
You're testing behaviour. You expect the history prop to be called with some value, so you need to mock it and pass it to your component.
const history = {push:jest.fn()}
And check if it is called

Testing a react component that is wrapped in react hot keys

Hi I am quite new to react. I have implemented key board shortcuts in my react application using react-hotkeys.
The plugin basically asks to wrap rendered code inside following tags
<Hotkeys>
// Component render output
</Hotkeys>
Now how to test this kind of components? I wrote a test case like this
it('Shows text passed', () => {
var expected = [1,2,3,0];
const wrapper = renderer.create(<HomePageRow title={'Home Row Title'}cards={[]}/>);
const inst = wrapper.getInstance();
expect(inst.render()).toMatchSnapshot();
});
It gives following error
Console
console.error node_modules\react-test-renderer\cjs\react-test-renderer.development.js:5530
The above error occurred in the <HotKeys> component:
in HotKeys (at homePageRow.js:68)
in HomePageRow (at home.test.js:14)
Consider adding an error boundary to your tree to customize error handling behavior.
What is this error boundary? How to ignore HOCs in testing? Did anyone implement react-hotkeys plugin and wrote tests to your component? If so can you please help me how to write them?
The WrappedComponent property of any Component can help you access component wrapped with HOC's without creating a mock for the HOC :
it('Shows text passed', () => {
var expected = [1,2,3,0];
const wrapper = renderer.create(<HomePageRow.WrappedComponent title={'Home Row Title'}cards={[]}/>);
const inst = wrapper.getInstance();
expect(inst.render()).toMatchSnapshot();
});
If it's not a HOC try just to mock Hotkeys :
jest.mock('../../../Hotkeys', () => (
jest.fn(() =>
<div>Hotkeys component</div>,
)
));

Resources