React Testing With Jest - Mock components on the same file except one - reactjs

I have a tsx file which contains three react components:
import {FC} from 'react';
export const ComponentA: FC<{booleanProp: boolean}> = ({booleanProp}) => {
return (
<>
{booleanProp ? (
<ComponentB />
) : (
<ComponentC />
)}
</>
);
};
export const ComponentB: FC = () => {
return <span>ComponentB</span>;
};
export const ComponentC: FC = () => {
return <span>ComponentC</span>;
};
I want to test ComponentA and mock ComponentB and ComponentC.
This is my test file:
import {FC} from 'react';
import {createRoot, Root} from 'react-dom/client';
import {act} from 'react-dom/test-utils';
import {ComponentA} from './my-components';
jest.mock('./my-components', () => {
const ComponentBMock: FC = () => {
return <span>ComponentB Mock</span>;
};
const ComponentCMock: FC = () => {
return <span>ComponentC Mock</span>;
};
return {
...jest.requireActual('./my-components'),
ComponentB: ComponentBMock,
ComponentC: ComponentCMock,
};
});
describe('ComponentA', () => {
let container: Element | null = null;
let root: Root | null = null;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => {
root?.unmount();
root = null;
});
container?.remove();
container = null;
});
it('should render "ComponentB" when booleanProp is true', () => {
act(() => {
root?.render(<ComponentA booleanProp={true}/>);
});
expect(container?.textContent).toEqual('ComponentB Mock');
});
it('should render "ComponentC" when booleanProp is false', () => {
act(() => {
root?.render(<ComponentA booleanProp={false}/>);
});
expect(container?.textContent).toEqual('ComponentC Mock');
});
});
The problem is that the mocks doesn't seem to take effect and these are the tests result:
Expected: "ComponentB Mock"
Received: "ComponentB"
Expected: "ComponentC Mock"
Received: "ComponentC"
When I debugged the jest mock callback it appears to be called twice. In the first time the requireActual returned undefined for every component, and in the second time it has the real components values.
What am I missing?
Thanks for your help!

So after playing with it and read more about mocking this solution solved my problem.
The difference is the way I import my components and used jest.spyOn to mock them:
import * as MyComponents from './my-components';
const ComponentA = MyComponents.ComponentA;
jest.spyOn(MyComponents, 'ComponentB').mockReturnValue(<span>ComponentB Mock</span>);
jest.spyOn(MyComponents, 'ComponentC').mockReturnValue(<span>ComponentC Mock</span>);
Of course that if you you need to remove the created mock you can restore the mock by calling the spy.mockRestore function.
The full test file:
import {createRoot, Root} from 'react-dom/client';
import {act} from 'react-dom/test-utils';
import * as MyComponents from './my-components';
const ComponentA = MyComponents.ComponentA;
jest.spyOn(MyComponents, 'ComponentB').mockReturnValue(<span>ComponentB Mock</span>);
jest.spyOn(MyComponents, 'ComponentC').mockReturnValue(<span>ComponentC Mock</span>);
describe('ComponentA', () => {
let container: Element | null = null;
let root: Root | null = null;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => {
root?.unmount();
root = null;
});
container?.remove();
container = null;
});
it('should render "ComponentB" when booleanProp is true', () => {
act(() => {
root?.render(<ComponentA booleanProp={true}/>);
});
expect(container?.textContent).toEqual('ComponentB Mock');
});
it('should render "ComponentC" when booleanProp is false', () => {
act(() => {
root?.render(<ComponentA booleanProp={false}/>);
});
expect(container?.textContent).toEqual('ComponentC Mock');
});
});

Related

Cannot read properties of undefined (reading 'find') in Jest and Enzyme React 17

I am working through a react app using v17. I have a component that adds an expense. The functionality in the component works as expected in the GUI but when I try to test it using jest/enzyme it throws an error of TypeError: Cannot read properties of undefined (reading 'find'). In the GUI it finds the expense I am trying to edit without issue. Am I not testing it correctly when trying to match a snapshot?
Edit Expense Component
import React from "react";
import { useParams } from "react-router-dom";
import { connect } from "react-redux";
import { ExpenseForm } from "./ExpenseForm";
import { editExpense, removeExpense } from "../actions/expenses";
//React router
import { useNavigate } from "react-router-dom";
export const EditExpensePage = (props) => {
const navigate = useNavigate();
const { expenseID } = useParams();
const foundExpense = props.expenses.find(
(expense) => expense.id === expenseID
);
return (
<div>
<h1>Edit Expense</h1>
<ExpenseForm
expense={foundExpense}
onSubmit={(expense) => {
// //Edit expense action expects 2 params (id, updates)
props.editExpense(expenseID, expense);
// //Redirect to dashboard
navigate("/");
}}
/>
<button
onClick={(e) => {
e.preventDefault();
props.removeExpense(expenseID);
navigate("/");
}}
>
Remove Expense
</button>
</div>
);
};
const mapStateToProps = (state, props) => ({
expenses: state.expenses
});
const mapDispatchToProps = (dispatch) => ({
editExpense: (id, expense) => dispatch(editExpense(id, expense)),
removeExpense: (id) => dispatch(removeExpense(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(EditExpensePage);
Current Test
import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";
let history, editExpense, removeExpense, wrapper;
//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
const setup = (props) => {
const component = shallow(
<EditExpensePage
{...props}
expense={editExpense}
history={history}
removeExpense={removeExpense}
/>
);
return {
component: component
};
};
describe("EditForm component", () => {
beforeEach(() => {
setup();
});
test("should render EditExpensePage", () => {
expect(wrapper).toMatchSnapshot();
});
});
Updated editExpense value in test
const setup = (props) => {
//editExpense = testExpenses[1]; //Same error
editExpense = jest.fn(); //Same error
let removeExpense = jest.fn();
let history = jest.fn();
const component = shallow(
<EditExpensePage
{...props}
expense={editExpense}
history={history}
removeExpense={removeExpense}
/>
);
return {
component: component
};
};
Updated Test File
import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";
let editExpense, expenseID, removeExpense, wrapper;
//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
const setup = (props) => {
expenseID = 1;
editExpense = [testExpenses.find((expense) => expense.id === expenseID)];
console.log(editExpense);
//Output from this console log
// [
// {
// id: 1,
// description: 'Wifi payment',
// note: 'Paid wifi',
// amount: 10400,
// createdAt: 13046400000
// }
// ]
const component = shallow(
<EditExpensePage
{...props}
expense={editExpense}
removeExpense={removeExpense}
/>
);
return {
component: component
};
};
describe("EditForm component", () => {
beforeEach(() => {
setup();
});
test("should render EditExpensePage", () => {
expect(wrapper).toMatchSnapshot();
});
});
Updated your code
import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";
let history, editExpense, removeExpense, wrapper;
//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
const setup = (props) => {
editExpense = testExpenses; //it should be an array like this [{ id: 1 }]
const component = shallow(
<EditExpensePage
{...props}
expenses={editExpense}
history={history}
removeExpense={removeExpense}
/>
);
return {
component: component
};
};
describe("EditForm component", () => {
beforeEach(() => {
setup();
});
test("should render EditExpensePage", () => {
expect(wrapper).toMatchSnapshot();
});
});
Your EditExpensePage is calling props.expenses, but in your test cases, you never set it up.
You only introduce it here
let history, editExpense, removeExpense, wrapper;
but you haven't set the value for editExpense which means it's undefined.
That's why undefined.find throws an error.
I'd suggest you set a mocked value for editExpense.

Jest testing: How do I correctly write expect assertions witin a setTimeout?

I want to JEST test a React Component which updates state after a timeout both before and after rendering state. The expect() assertion works fine in a test() but breaks when used inside a SetTimeout callback. An error states that expect is not defined within the setTimeout callback.
I have created a sandbox here:
https://codesandbox.io/s/jest-delayed-render-test-j5b7l
Simple component update state after SetTimeout.
// delayed.js
import { useEffect, useState } from "react";
export const Delayed = () => {
let [value, setValue] = useState("foo");
useEffect(() => {
let handle = setTimeout(() => {
setValue("bar");
}, 3000);
return () => {
clearTimeout(handle);
};
}, []);
return (
<>
<h1>MyPromise</h1>
<p>value = {value}</p>
</>
);
};
Simple test asserts the component before and update timeout
// delayed.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import { Delayed } from "./delayed";
let container = null;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("checks initial value", async () => {
await act(async () => {
render(<Delayed />, container);
});
expect(container.textContent).toContain("foo");
});
it("checks final value", async () => {
await act(async () => {
render(<Delayed />, container);
});
setTimeout(() => {
// this doesnt get executed!
// error: expect is not defined
expect(container.textContent).toContain("1234");
}, 2000);
});
this is easily fixed passing in done as a param on the test.
it("checks final value", async (done) => {
await act(async () => {
render(<Delayed />, container);
});
setTimeout(() => {
expect(container.textContent).toContain("bar");
done();
}, 3000);
});
as described here:
https://www.pluralsight.com/guides/test-asynchronous-code-jest

How to test custom hooks in React using JEST, Enzyme?

I have a Custom Hook like below
const useSum = (a = 1, b = 1) => {
const [sum, setSum] = useState(a + b);
useEffect(() => {
setSum(a + b);
}, [a, b]);
return sum;
}
I am using this in my funcionat component
const MyFuncComp = () => {
const sum = useSum(1, 2);
return (
<div>{sum}</div>
);
}
In test case I have it like this
describe('Testing MyFuncComp', () => {
const myFuncComp = mount(<MyFuncComp />);
it('should have value of sum', () => {
const expected = '3';
const received = myFuncComp.find('div').text();
expect(received).toEqual(expected);
});
})
It's never executing 'useState' or 'useEffect'. Received value is always 'undefined';
I recommend you to use: #testing-library/react-hooks
import { renderHook } from '#testing-library/react-hooks';
describe('Testing MyFuncComp', () => {
it('should have value of sum', () => {
const myFuncComp = renderHook(() => useSum(1,2));
const expected = '3';
const received = myFuncComp.result.current;
expect(received).toEqual(expected);
});
})
Also I don't think you need enzyme or any lib to test your component, you can use react-dom and react-dom/test-utils
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import MyFunComp from "./MyFunComp";
let container = null;
describe("Card component", () => {
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("Should render correctly", async () => {
await act(async () => {
render(<MyFunComp />, container);
});
const div = container.querySelector("div");
expect(div).toBeTruthy();
expect(div.textContent).toBe("123");
});
});

How can I test a React Hooks component by changing useState

I have a React hooks functional component that I'd like to test with Jest/Enzyme. I would like test its tertiary render behaviour based upon a useState value. I can't seem to find any example online. There is no 'click' to simulate - no API call to mock because at the end, I still need to test based upon the useState value.
In the past, with class components, I could set the state. With the new hooks, I can't.
So, basically - how do I mock an async await inside a mocked submitForm function so that the render behaves properly?
Here's my component:
import React, { useState } from 'react';
import { Redirect } from 'react-router-dom';
import Form from 'core/Form';
export const Parent = ({submitForm}) => {
const [formValues, setFormValues] = useState({});
const [redirect, setRedirect] = useState(false);
const handleChange = name => evt => {
setFormValues({ ...formValues, [name]: evt.target.value });
};
const onSubmit = async () => {
try {
const res = await submitForm(formValues);
if (res) setRedirect(true);
else setRedirect(false);
} catch (err) {
console.log('Submit error: ', err);
}
};
return redirect ? (
<Redirect push to={path} />
) : (
<Form onSubmit={onSubmit} values={formValues} onChange={handleChange} />
);
};
export default Parent;
Here's my testing so far:
import React from 'react';
import { shallow } from 'enzyme';
import { Redirect } from 'react-router-dom';
import Parent from './Parent';
import Form from 'core/Form';
let wrapper, props;
.
.
.
describe('<Parent /> rendering', () => {
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<Parent {...props} />);
});
afterEach(() => {
jest.clearAllMocks();
});
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, 'useState');
useStateSpy.mockImplementation(init => [init, setState]);
it('Should render 1 Form', () => {
expect(wrapper.find(Form)).toHaveLength(1);
});
it('renders Redirect after API call', () => {
setRedirect = jest.fn(() => false);
expect(wrapper.find(Redirect)).toHaveLength(1);
});
it('renders Form before API call', () => {
setRedirect = jest.fn(() => true);
expect(wrapper.find(Form)).toHaveLength(1);
});
});
You don't need to spy useState hook. Which means you should not test these hooks and methods of the component directly. Instead, you should test components' behavior(the state, props and what is rendered)
E.g.
index.tsx:
import React, { useState } from 'react';
import { Redirect } from 'react-router-dom';
export const Form = ({ onSubmit, onChange, values }) => <form onSubmit={onSubmit}></form>;
const path = '/user';
export const Parent = ({ submitForm }) => {
const [formValues, setFormValues] = useState({});
const [redirect, setRedirect] = useState(false);
const handleChange = (name) => (evt) => {
setFormValues({ ...formValues, [name]: evt.target.value });
};
const onSubmit = async () => {
try {
const res = await submitForm(formValues);
if (res) setRedirect(true);
else setRedirect(false);
} catch (err) {
console.log('Submit error: ', err);
}
};
return redirect ? (
<Redirect push to={path} />
) : (
<Form onSubmit={onSubmit} values={formValues} onChange={handleChange} />
);
};
export default Parent;
index.test.tsx:
import Parent, { Form } from './';
import React from 'react';
import { shallow } from 'enzyme';
import { Redirect } from 'react-router-dom';
import { act } from 'react-dom/test-utils';
const whenStable = async () =>
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
describe('60137762', () => {
it('should render Form', () => {
const props = { submitForm: jest.fn() };
const wrapper = shallow(<Parent {...props}></Parent>);
expect(wrapper.find(Form)).toBeTruthy();
});
it('should handle submit and render Redirect', async () => {
const props = { submitForm: jest.fn().mockResolvedValueOnce(true) };
const wrapper = shallow(<Parent {...props}></Parent>);
wrapper.find(Form).simulate('submit');
await whenStable();
expect(props.submitForm).toBeCalledWith({});
expect(wrapper.find(Redirect)).toBeTruthy();
});
it('should handle submit and render Form', async () => {
const props = { submitForm: jest.fn().mockResolvedValueOnce(false) };
const wrapper = shallow(<Parent {...props}></Parent>);
wrapper.find(Form).simulate('submit');
await whenStable();
expect(props.submitForm).toBeCalledWith({});
expect(wrapper.find(Form)).toBeTruthy();
});
it('should handle error if submit failure', async () => {
const logSpy = jest.spyOn(console, 'log');
const mError = new Error('network');
const props = { submitForm: jest.fn().mockRejectedValueOnce(mError) };
const wrapper = shallow(<Parent {...props}></Parent>);
wrapper.find(Form).simulate('submit');
await whenStable();
expect(props.submitForm).toBeCalledWith({});
expect(logSpy).toHaveBeenCalledWith('Submit error: ', mError);
});
});
Unit test results with coverage report:
PASS stackoverflow/60137762/index.test.tsx
60137762
✓ should render Form (18ms)
✓ should handle submit and render Redirect (15ms)
✓ should handle submit and render Form (8ms)
✓ should handle error if submit failure (18ms)
console.log node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
Submit error: Error: network
at /Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:39:20
at step (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:44:23)
at Object.next (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:25:53)
at /Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:19:71
at new Promise (<anonymous>)
at Object.<anonymous>.__awaiter (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:15:12)
at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:37:47)
at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:100:37)
at resolve (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
at new Promise (<anonymous>)
at mapper (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at promise.then (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 78.57 | 100 | 40 | 93.75 |
index.tsx | 78.57 | 100 | 40 | 93.75 | 12
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 3.716s, estimated 5s
Source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60137762

React / React Native + Enzyme: How to test if a component renders it's children?

I have a component with which I wrap all my screens:
import React from "react";
import { SafeAreaView } from "react-native";
import { PropTypes } from "prop-types";
import styles from "./styles";
const SafeContainer = ({ children }) => {
return <SafeAreaView style={styles.container}>{children}</SafeAreaView>;
};
SafeContainer.propTypes = {
children: PropTypes.any
};
export default SafeContainer;
I'm currently writing it's unit tests. I would like to test if this component renders its children. How can I do this?
Here is the code that I've written (but even though the component works, the test does not pass):
import React from "react";
import { shallow } from "enzyme";
import SafeContainer from "./SafeContainer";
describe("SafeContainer", () => {
describe("rendering", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<SafeContainer />);
});
it("should render a <SafeAreaView />", () => {
expect(wrapper.find("SafeAreaView")).toHaveLength(1);
});
it("should render its children", () => {
expect(wrapper.children()).toEqual(wrapper.props().children);
});
});
});
How could I write a test that checks if the component renders it's children?
EDIT: Through the answer from eramit I implemented this check like this:
describe("SafeContainer", () => {
describe("rendering", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(
<SafeContainer>
<View className="Test" />
</SafeContainer>
);
});
it("should render a <SafeAreaView />", () => {
expect(wrapper.find("SafeAreaView")).toHaveLength(1);
});
it("should render its children", () => {
expect(wrapper.find(".Test")).toHaveLength(1);
});
});
});
You could create some test child div and check.
describe("SafeContainer", () => {
describe("rendering", () => {
let wrapper;
beforeEach(() => {
const TestComponent = <SafeContainer><div className="Test"/></SafeContainer>
wrapper = mount(<TestComponent />);
});
it("should render a <SafeAreaView />", () => {
expect(wrapper.find("SafeAreaView")).toHaveLength(1);
});
it("should render its children", () => {
expect(wrapper.find(".Test")).toHaveLength(1);
});
});
});

Resources