Mocking function in React class component using Jest produces "type is invalid" error - reactjs

I'm writing tests using Jest for a custom DataTable component. I'm also using React Testing Library.
The component loads an iframe when a row is clicked. I'm attempting to mock the function that loads the iframe and I'm getting an error message when running the test that uses the mock. For context, the goal of mocking this function is preventing the test from loading the actual iframe.
The full error message is: Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
My understanding is that this error message usually happens when you're incorrectly importing a module. However, I don't believe this is my case because the component is being imported and used in other tests and it works fine in those tests. It's only the component with the mocked function that produces this error.
How would I mock this function? The function is loadIframe(id) and it's in a class component. The code for the component and the test code is below.
Stripped down version of the component with the function I want to mock:
class MyDataTable extends React.Component {
...
// I want to mock this function
loadIframe(id = "") {
this.setState({iframe: `/path/to/iframe/${id}`})
}
...
render() {
return (
...
{this.state.iframe !== "" &&
<iframe src={this.state.iframe}/>
}
...
)
);
}
export default MyDataTable;
Code for the tests:
import "#testing-library/jest-dom";
import { render, screen, waitFor } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import axios from "axios";
import MyDataTable from "../../components/DataTable/MyDataTable";
jest.mock("axios");
jest.mock("../../components/DataTable/MyDataTable", () => ({
...jest.requireActual("../../components/DataTable/MyDataTable"),
loadIframe: jest.fn((id) => {
// this mocked implementation will need to do something, but I've removed it from this example
})
}));
describe("My tests", () => {
it("should load iframe on row click", async () => {
const data = [
["row 1, col 1", "row 1, col 2"],
["row 2, col 1", "row 2, col 2"]
];
// Need to make a mock POST request to get data so a row can be clicked
axios.post.mockResolvedValue({ data });
// The error is thrown when render() is called here
render(<MyDataTable id="test_id"/>);
await waitFor(() => expect(axios.post).toHaveBeenCalled());
userEvent.click(screen.getAllByRole("gridcell")[0]);
expect(screen.getByTitle("iframe")).toBeInTheDocument();
});
});

IMO, This case is unnecessary to test logic on function loadIframe. You just query selector that you wanna test and expect src attribute should be a value according to state change from function loadIframe.
jest.mock("axios");
describe("My tests", () => {
it("should load iframe on row click", async () => {
const data = [
["row 1, col 1", "row 1, col 2"],
["row 2, col 1", "row 2, col 2"]
];
// Need to make a mock POST request to get data so a row can be clicked
axios.post.mockResolvedValue({ data });
// The error is thrown when render() is called here
const {container} = render(<MyDataTable id="test_id"/>);
await waitFor(() => expect(axios.post).toHaveBeenCalled());
userEvent.click(screen.getAllByRole("gridcell")[0]);
const iFrame = container.querySelector('iframe')
// this step is expect `src` attribute in iFrame element should be according to state change inside function `loadiFrame`
expect(iFrame).toHaveAttribute('src','/path/to/iframe/1')
});
});

Related

React testing library unable to detect datagrid row cells

Current behavior/issue:
Using react testing libraries queries, I am not able to detect row cells or the data inside them, even after implementing the correct queries for data not available immediately.
Expected behavior:
Using forBy queries should result in a passing testing and show those rendered rows in screen.debug.
Code/Steps to reproduce:
import { render, screen } from '#testing-library/react';
import PerformanceDataGridModal from '../../features/PerformanceDataGridModal/PerformanceDataGridModal';
import ChartMockData from '../../utils/Mocks/ChartsMockData/ChartMockData';
import '#testing-library/jest-dom';
describe('Performance datagrid modal', () => {
test.each(ChartMockData)('opens with correct information based on %s button click', async (item) => {
const CloseDataGrid = jest.fn();
const ClosedDataGridModal = (
<PerformanceDataGridModal
open={false}
onclose={CloseDataGrid}
rows={ChartMockData[item.id].rows}
columns={ChartMockData[item.id].columns}
/>
);
const OpenedDataGridModal = (
<PerformanceDataGridModal
open
onclose={CloseDataGrid}
rows={ChartMockData[item.id].rows}
columns={ChartMockData[item.id].columns}
/>
);
render(ClosedDataGridModal);
expect(screen.queryByRole('dialog')).toBeFalsy();
expect(screen.queryByRole('grid')).toBeFalsy();
expect(screen.queryByRole('cell', { name: 'Yes' })).toBeFalsy();
render(OpenedDataGridModal);
expect(screen.getByRole('dialog')).toBeTruthy();
expect(screen.getByRole('grid')).toBeTruthy();
expect(await screen.findByRole('cell', { name: 'jane' })).toBeTruthy();
screen.debug();
});
});
As you see on this line
expect(await screen.findByRole('cell', { name: 'jane' })).toBeTruthy();
I have followed the instructions of react testing library as indicated here:
https://testing-library.com/docs/guide-disappearance
What I've tried?
Await WaitFor, with getBy queries instead of forBy queries
using disableVirtualization as indicated in material mui datagrid section and the following source: https://github.com/mui/mui-x/issues/1151
using jest.requierActual datagrid and setting autoHeight
awaiting the render of component
await the entire assertion(expect....

jest mockReturnValueOnce is not a function

I have a custom hook that returns true or false by checking if the view is a mobile or a desktop view.
I'm trying to write a test for a page that uses this hook. I tried as following
import { useIsMobile } from '../../../../hooks/useIsMobile';
jest.mock('../../../../hooks/useIsMobile');
describe('Form', () => {
beforeEach(jest.resetAllMocks);
it('should render the correct header style for deliver to label in desktop view', () => {
(useIsMobile as jest.Mock).mockReturnValueOnce(false);
const delivery_label = screen.getByTestId('label1');
expect(delivery_label.tagName).toEqual('H3');
});
it('should render the correct header style for deliver to label in mobile view', () => {
(useIsMobile as jest.Mock).mockReturnValueOnce(true);
const delivery_label = screen.getByTestId('label1');
expect(delivery_label.tagName).toEqual('H4');
});
}
But I'm getting this error
TypeError: _useIsMobile.useIsMobile.mockReturnValueOnce is not a function
What does this mean and how can i fix this?

Describe method can only pass with 1 test unless re-rendering each component again and again

I'm trying to figure out why my test - which passes when ran alone - is failing whenever the describe block contains more than 1 test. Take this example, which I've taken from my real code and simplified:
describe('Create Account Form', () => {
const {container} = render(<CreateAccountForm />);
const email = container.querySelector('input[name="email"]');
const password1 = container.querySelector('input[name="password1"]');
it('Should render all fields', () => {
allInputs.forEach((input) => {
expect(input).toBeInTheDocument();
});
});
it('Another test', () => {
expect(email).toBeInTheDocument(); // fails
});
});
The 2nd test fails, but passes only when commenting out the first test, or re-rendering the container again in the test like this:
it('Another test', () => {
const {container} = render(<CreateAccountForm />);
const email = container.querySelector('input[name="email"]');
expect(email).toBeInTheDocument(); // passes
});
Why does this have to happen? I would much rather not have to re-render the container and declare new variables inside each test block.
Thank you
RTL will unmount React trees that were mounted with render in afterEach hook. See cleanup.
Please note that this is done automatically if the testing framework you're using supports the afterEach global and it is injected to your testing environment (like mocha, Jest, and Jasmine).
Move the render code into beforeEach or individual test case. So that we can create react trees before each test case. Isolate test cases from each other, using their own test data without affecting the rest.
E.g.
index.tsx:
import React from 'react';
export function Example() {
return (
<div>
<input name="email" />
<input name="password1" />
</div>
);
}
index.test.tsx:
import { render } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import React from 'react';
import { Example } from './';
describe('70753645', () => {
let email, password1, allInputs;
beforeEach(() => {
const { container } = render(<Example />);
email = container.querySelector('input[name="email"]');
password1 = container.querySelector('input[name="password1"]');
allInputs = container.querySelectorAll('input');
});
it('Should render all fields', () => {
allInputs.forEach((input) => {
expect(input).toBeInTheDocument();
});
});
it('Another test', () => {
expect(email).toBeInTheDocument();
});
});
Test result:
PASS stackoverflow/70753645/index.test.tsx (9.222 s)
70753645
✓ Should render all fields (24 ms)
✓ Another test (3 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.717 s
package versions:
"#testing-library/react": "^11.2.2",
"jest": "^26.6.3",

Mock function doesn't get called when inside 'if' statement - React app testing with jest and enzyme?

I am writing a test case for my react app and I'm trying to simulate a button click with a mock function. I'm passing the mock function as a prop and I'm calling the function inside an 'if' statement but the mock function doesn't get called and the test fails but if i call the function without the 'if' statement it gets called and the test passes. Why is this happening?
Form.js
const Form = ({ text, incompleteList, setIncompleteList }) => {
const submitTodoHandler = (e) => {
e.preventDefault()
if (text !== '') {
setIncompleteList([...incompleteList, { name: text, id: Math.random() * 1000 }])
}
}
return (
<form action='' autoComplete='off'>
<button type='submit' className='todo-button' onClick={submitTodoHandler}>
add
</button>
</form>
)
}
export default Form
Form.test.js
import Enzyme, { shallow, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Form from '../components/Form'
Enzyme.configure({ adapter: new Adapter() })
test('Form calls setIncompleteList prop on add button onClick event', () => {
const mockfn = jest.fn()
const wrapper = mount(<Form setIncompleteList={mockfn} />)
wrapper.find('button').simulate('click')
expect(mockfn).toHaveBeenCalled()
})
I'm using react 16.
The problem was I did not pass the 'text' props to the form component and the comparison failed to take place that's why the mock doesn't get called and the test failed.
<Form text='mock' setIncompleteList={mockfn} />
Pass value and incompleteList while mounting the component
test('Form calls setIncompleteList prop on add button onClick event', () => {
const mockfn = jest.fn()
const wrapper = mount(<Form text='mock'
incompleteList={[{name: 'sarun', id: 1001}]} setIncompleteList={mockfn} />)
wrapper.find('button').simulate('click')
expect(mockfn).toHaveBeenCalled()
})
you can also set a default value for incompletelist like below so that no need to pass incompletelist while mounting the component,
const Form = ({ text, incompleteList = [], setIncompleteList }) => {
}

Why is first Jest test causing second test to fail?

I have a React component which renders a list of components. I'm running some tests which mock the axios module which loads in the users from JSONPlaceHolder. All works fine including the the async test and it's mocks data as expected. However if you look at the code below it only passes as long as the first test is commented out? Am I missing something? Been banging my head for ages. Is there some cleanup that needs to be done between tests? Thanks in advance.
import { waitForElement } from 'enzyme-async-helpers';
import UsersList from '../UsersList';
import axios from 'axios';
const mockUsers = [
{
"id": 1,
"name": "Leanne Mock",
"username": "Bret",
"email": "Sincere#april.biz"
},
{
"id": 2,
"name": "John Mock",
"username": "Jospeh",
"email": "wacky#april.biz"
}
]
axios.get.mockImplementationOnce(() => Promise.resolve({
data: mockUsers
}))
describe('<UsersList /> tests:', () => {
//WHEN I UNCOMMENT THIS TEST THE SECOND TEST FAILS?
test('It renders without crashing', (done) => {
// const wrapper = shallow(<UsersList />);
});
test('It renders out <User /> components after axios fetches users', async () => {
const wrapper = shallow(<UsersList />);
expect(wrapper.find('#loading').length).toBe(1); //loading div should be present
await waitForElement(wrapper, 'User'); //When we have a User component found we know data has loaded
expect(wrapper.find('#loading').length).toBe(0); //loading div should no longer be rendered
expect(axios.get).toHaveBeenCalledTimes(1);
expect(wrapper.state('users')).toEqual(mockUsers); //check the state is now equal to the mockUsers
expect(wrapper.find('User').get(0).props.name).toBe(mockUsers[0].name); //check correct data is being sent down to User components
expect(wrapper.find('User').get(1).props.name).toBe(mockUsers[1].name);
})
})
The Error message I get is:
The render tree at the time of timeout:
<div
id="loading"
>
Loading users
</div>
console.warn node_modules/enzyme-async-helpers/lib/wait.js:42
As JSON:
{ node:
{ nodeType: 'host',
type: 'div',
props: { id: 'loading', children: ' Loading users ' },
key: undefined,
ref: null,
instance: null,
rendered: ' Loading users ' },
type: 'div',
props: { id: 'loading' },
children: [ ' Loading users ' ],
'$$typeof': Symbol(react.test.json) }
Test Suites: 1 failed, 1 total
Tests: 2 failed, 2 total
You only mock the first axios.get call because you are using mockImplementationOnce.
When you shallow(<UsersList />) twice, the second time is timing out loading the users.
You can add a beforeEach method with a mockResolvedValueOnce inside, to mock the axios.get before every single test:
beforeEach(() => {
axios.get.mockResolvedValueOnce({data: mockUsers});
}
Having the same issue, but I'm not making a request. I'm building a client-side React application and testing for the render of sub-components. I have an image carousel that loads on my Home component and I'm writing tests for it. If I comment out all but one test (any test) it passes. If I have more than one test (any combination of tests), it fails. I've tried async/await/waitFor, react-test-renderer, using done() - nothing seems to change this behavior.
import { render, screen } from '#testing-library/react';
import ImageCarousel from '../carousel/ImageCarousel';
import localPhotos from '../../data/localPhotos';
// passing in the full array of images is not necessary, it will cause the test to time out
const testArray = localPhotos.slice(0, 3);
describe('Image Carousel', () => {
it('renders without error', () => {
render(<ImageCarousel images={testArray} />);
const imageCarousel = screen.getByTestId('image-carousel');
expect(imageCarousel).toBeInTheDocument();
});
// it('displays the proper alt text for images', () => {
// render(<ImageCarousel images={testArray} />);
// const photo1 = screen.getByAltText(localPhotos[0].alt);
// const photo2 = screen.getByAltText(localPhotos[1].alt);
// expect(photo1.alt).toBe(localPhotos[0].alt);
// expect(photo2.alt).toBe(localPhotos[1].alt);
// });
// it("displays full-screen icons", () => {
// render(<ImageCarousel images={testArray} />);
// const fullScreenIcons = screen.getAllByTestId('full-screen-icon');
// expect(fullScreenIcons.length).toBe(testArray.length);
// })
// shows controls when showControls is true
// does not show controls when showControls is false
// it('displays the proper number of images', () => {
// render(<ImageCarousel images={testArray} />);
// const carousel_images = screen.getAllByTestId('carousel_image');
// expect(carousel_images.length).toBe(testArray.length);
// });
// calls next when clicked
// calls previous when clicked
// returns to first image when next is clicked and last image is shown
// moves to last image when previous is clicked and first image is shown
});

Resources