Testing with jest on component with custom hook - reactjs

i'm trying to write test code for my component that uses a custom hook to seperate logic from view
The problem is that i cannot for whatever reason seem to actually mock this custom hook in a test.
Following is a code example of what im trying to do:
// click-a-button.tsx
import {useClickAButton} from "./hooks/index";
export const ClickAButton = () => {
const { handleClick, total } = useClickAButton();
return <button onClick={handleClick}>{total}</button>;
}
// hooks/use-click-a-button.tsx
import React, {useCallback, useState} from 'react';
export const useClickAButton = () => {
const [total, setTotal] = useState<number>(0);
const handleClick = useCallback(() => {
setTotal(total => total + 1);
}, []);
return {
handleClick,
total,
};
}
// click-a-button.test.tsx
import * as React from 'react';
import {act} from "react-dom/test-utils";
import {render} from "#testing-library/react";
import {useClickAButton} from './hooks/index'
import {ClickAButton} from "./index";
const hooks = { useClickAButton }
test('it runs with a mocked customHook',() => {
const STATE_SPY = jest.spyOn(hooks, 'useClickAButton');
const CLICK_HANDLER = jest.fn();
STATE_SPY.mockReturnValue({
handleClick: CLICK_HANDLER,
total: 5,
});
const component = render(<ClickAButton />);
expect(component.container).toHaveTextContent('5');
act(() => {
component.container.click();
});
expect(CLICK_HANDLER).toHaveBeenCalled();
})
When running the test, neither of the expects is fulfilled.
Context gets to be 0 instead of the mocked 5 and the CLICK_HANDLER is never called.
All in all it seems that the jest.spyon has no effect.
Please help

it seems i found the answer myself.
// right after imports in test file
jest.mock('./hooks')
is all that it took!

Related

Jest Mock returns undefined instead of value

I am using Jest to test a react component. I am trying to mock a function from other dependency. The function from dependency should return an array, but it is showing undefined on the console.
Below file is the tsx file, when I click the button, it should call the dependency function to get the list of the Frames.
ExitAppButton.tsx:
import React, { useContext, useState } from 'react';
import { TestContext } from '../ContextProvider';
import { useDispatch } from 'react-redux';
const ExitAppButton = (props: any): JSX.Element => {
const { sdkInstance } = useContext(TestContext);
const exitAppClicked = () => {
const appList = sdkInstance.getFrames().filter((app: any) => {app.appType === "Test App"}).length}
test file, SignOutOverlay.test.tsx:
import * as React from 'react';
import { fireEvent, render, screen } from '#testing-library/react';
import SignOutOverlay from '.';
import ExitAppButton from './ExitAppButton';
import { TestContext } from '../ContextProvider';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
const api = require('#praestosf/container-sdk/src/api');
const mockStore = configureStore([]);
jest.mock('#praestosf/container-sdk/src/api');
api.getFrames.mockReturnValue([{appType:"Test App"},{appType:"Test App"},{appType:"Not Test App"}]);
describe('Test Exit app Button', () => {
const renderExitAppButton = () => {
const store = mockStore([{}]);
render(
<Provider store={store}>
<TestContext.Provider value={{ sdkInstance: api }}>
<SignOutOverlay>
<ExitAppButton/>
</SignOutOverlay>
</TestContext.Provider>
</Provider>
);
};
it('should to be clicked and logged out', () => {
renderExitAppButton();
fireEvent.click(screen.getByTestId('exit-app-button-id'));
});
This is the dependency file, api.js
const getFrames = () => {
let frames = window.sessionStorage.getItem('TestList');
frames = frames ? JSON.parse(frames) : [];
return frames
};
const API = function () { };
API.prototype = {
constructor: API,
getFrames
};
module.exports = new API();
I mocked the getFrame function to return an array of 3 objects, but when running the test case, it is returning undefined. Below error was showing:
TypeError: Cannot read property 'filter' of undefined
Am I mocking this correct?
I think it's because api.getFrames is undefined and not a mock.
Try changing your mock statement to this:
jest.mock('#praestosf/container-sdk/src/api', () => ({
getFrames: jest.fn(),
// add more functions if needed
}));
Turns out, I have the other file with the same test name which is causing the problem. I am beginner for Jest, a tip for developer like me, we should always run test case file alone using
jest file.test.tsx
Not all files at a time:
jest

Using querySelectorAll instead of querySelector for testing with toBeInTheDocument

Is it possible to use querySelectorAll with jest react tests rather than individually selecting each component with querySelector and checking that they're in the document with toBeInTheDocument?
For example, testing a component like this:
const SomeComponent = () => (
<>
<p id='one'>one</p>
<p id='two'>two</p>
<p id='three'>three</p>
</>
)
Would otherwise require writing a test like this:
import React from 'react';
import {render} from '#testing-library/react';
import '#testing-library/jest-dom';
describe('Some Component', () => {
it('Should render all', () => {
const {container} = render(<SomeComponent/>);
const one = container.querySelector('#one');
const two = container.querySelector('#two');
const three = container.querySelector('#three');
expect(one).toBeInTheDocument();
expect(two).toBeInTheDocument();
expect(three).toBeInTheDocument();
});
});
I have a list of numerous elements which is starting to get quite lengthy.
Check the number of the p element and use for...loop to check each one of them is in the document or not. So you don't need to assert them one by one.
import React from 'react';
import { render } from '#testing-library/react';
import '#testing-library/jest-dom';
import { SomeComponent } from '.';
describe('Some Component', () => {
it('Should render all', () => {
const { container } = render(<SomeComponent />);
const matches = container.querySelectorAll('p');
expect(matches).toHaveLength(3);
matches.forEach((m) => {
expect(m).toBeInTheDocument();
});
});
});

Create React App - How to mock globally your own component

I have a component that I would like to mock for my test, which exists in src/components/shared/DebouncedInput.js and it looks like this:
import { useState, useCallback } from 'react'
import debounce from 'lodash.debounce'
const useDebounce = (callback, delay) => {
const debouncedFn = useCallback(
debounce((...args) => callback(...args), delay),
[delay] // will recreate if delay changes
);
console.log('debouncedFn', debouncedFn);
return debouncedFn;
};
function DebouncedInput(props) {
const [value, setValue] = useState(props.value);
const debouncedSave = useDebounce(
(nextValue) => props.onChange(nextValue),
props.delay
);
const handleChange = (nextValue) => {
setValue(nextValue);
debouncedSave(nextValue);
};
return props.renderProps({ onInputChange: handleChange, value });
};
export default DebouncedInput;
I also have mocks for third party libraries in src/mocks folder.
Mocks for them work fine in my tests, but I am not sure where to put a mock for my own component DebouncedInput that I would like to mock so that it is globally available to my tests:
import React from "react";
function DebouncedInput(props) {
const handleChange = (nextValue) => props.onChange(nextValue);
return props.renderProps({ onInputChange: handleChange, value: props.value });
};
export default DebouncedInput;
I have put it in the src/components/shared/mocks/DebouncedInput.js folder, but that my test is still hitting the original implementation and not the mock. How should I implement this mock?

How to write a test for conditional rendering component depended on useState hook in React?

I'm trying to write a test for my functional component, but don't understand how to mock isRoomsLoaded to be true, so I could properly test my UI. How and what do I need to mock?
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchRooms } from '../../store/roomsStore'; // Action creator
// Rooms component
export default ({ match, location, history }) => {
const roomsStore = useSelector(state => state.rooms);
const dispatch = useDispatch();
const [isRoomsLoaded, setRoomsLoaded] = useState(false);
useEffect(() => {
const asyncDispatch = async () => {
await dispatch(fetchRooms());
setRoomsLoaded(true); // When data have been fetched -> render UI
};
asyncDispatch();
}, [dispatch]);
return isRoomsLoaded
? <RoomsList /> // Abstraction for UI that I want to test
: <LoadingSpinner />;
};
If you want, you could flat out mock useState to just return true or false, to get whichever result you want by doing the following.
const mockSetState = jest.fn();
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: value => [true, mockSetState],
}));
By doing this, you're effective mocking react, with react, except useState, its a bit hacky but it'll work.

Props aren't passing inside component in test cases written with Jest and Enzyme

This is my test case
import React from 'react';
import { mount } from 'enzyme';
import CustomForm from '../index';
describe('Custom Form mount testing', () => {
let props;
let mountedCustomForm;
beforeEach(() => {
props = {
nav_module_id: 'null',
};
mountedCustomForm = undefined;
});
const customform = () => {
if (!mountedCustomForm) {
mountedCustomForm = mount(
<CustomForm {...props} />
);
}
return mountedCustomForm;
};
it('always renders a div', () => {
const divs = customform().find('div');
console.log(divs);
});
});
Whenever I run the test case using yarn test. It gives the following error TypeError: Cannot read property 'nav_module_id' of undefined.
I have already placed console at multiple places in order to see the value of props. It is getting set. But it couldn't just pass through the components and give the error mentioned above.
Any help would be appreciated been stuck for almost 2-3 days now.
You have to wrap the component that you want to test in beforeEach method such that it becomes available for all the 'it' blocks, and also you have to take the mocked props that you think you are getting into the original component.
import React from 'react'
import {expect} from 'chai'
import {shallow} from 'enzyme'
import sinon from 'sinon'
import {Map} from 'immutable'
import {ClusterToggle} from '../../../src/MapView/components/ClusterToggle'
describe('component tests for ClusterToggle', () => {
let dummydata
let wrapper
let props
beforeEach(() => {
dummydata = {
showClusters: true,
toggleClustering: () => {}
}
wrapper = shallow(<ClusterToggle {...dummydata} />)
props = wrapper.props()
})
describe(`ClusterToggle component tests`, () => {
it(`1. makes sure that component exists`, () => {
expect(wrapper).to.exist
})
it('2. makes sure that cluster toggle comp has input and label', () => {
expect(wrapper.find('input').length).to.eql(1)
expect(wrapper.find('label').length).to.eql(1)
})
it('3. simulating onChange for the input element', () => {
const spy = sinon.spy()
const hct = sinon.spy()
hct(wrapper.props(), 'toggleClustering')
spy(wrapper.instance(), 'handleClusterToggle')
wrapper.find('input').simulate('change')
expect(spy.calledOnce).to.eql(true)
expect(hct.calledOnce).to.eql(true)
})
})
})

Resources