How can I mock useQuery from react-fetching-library? - reactjs

Hello how can I mock useQuery? I have container component which is responsible for calling an api, and simple ui component to display data for the user. I'm receiving current error
console.error node_modules/#testing-library/react/dist/act-compat.js:52
Error: Error: connect ECONNREFUSED 127.0.0.1:80
Container
import React from 'react';
import { screen, waitForElement, getByText } from '#testing-library/react';
import { useQuery } from 'react-fetching-library';
import { render } from 'tests';
import tags from 'api/mocks/tags-response.json';
import { TrendingTagsContainer } from './TrendingTagsContainer';
jest.mock('react-fetching-library');
describe('TrendingTagsContainer component', () => {
test('should render component with correct title and description', async () => {
const action = jest.fn();
const useQuery = jest.fn(action());
useQuery.mockReturnValue({ loading: false, error: false, query: () => true, payload: { tags } });
console.log(useQuery());
const { getByText } = render(<TrendingTagsContainer />);
await waitForElement(() => screen.getByText('#Testing react'));
expect(screen.getByText('#Testing react')).toBeInTheDocument();
});
});

I think you can simply you mock your react-fetching-library module as following:
import { useQuery } from 'react-fetching-library';
jest.mock('react-fetching-library');
// Everything looks the same I guess

Related

How to call expect in #testing-library/react

Here is my code:
import React from 'react';
import { render, screen, waitFor } from '#testing-library/react';
import user from '#testing-library/user-event';
import FirebaseLogin from './AuthLogin';
import i18n from '../../../../i18nForTests';
import { I18nextProvider } from 'react-i18next';
test('shows error message when email is empty', async () => {
i18n.changeLanguage('en');
const comp = render(<I18nextProvider i18n={i18n}><FirebaseLogin /></I18nextProvider>);
const emailInput = getEmail();
await user.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(getEmail()).toHaveErrorMessage('Email is required');
});
});
function getEmail() {
return screen.getByRole('textbox', { name: /email address/i});
}
When I run this test via npm test, it write the following error:
TypeError: expect(...).toHaveErrorMessage is not a function
Do you know, what is wrong wiht my settings or code?

Get TypeError: Cannot redefine property: useNavigate when using Jest spyOn

I am using Jest + React Testing Library to test my App.
The test file:
import { render, screen, waitFor } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import React from "react";
import { MemoryRouter } from "react-router-dom";
import GoBack from "../Goback";
const mockUseNavigate = jest.spyOn(require("react-router-dom"), "useNavigate");
const navigate = mockUseNavigate()
describe("GoBack page", () => {
const pageInfo =
"This is error page!";
it("renders the GoBack page", async () => {
const { container } = render(
<MemoryRouter>
<GoBack />
</MemoryRouter>
);
expect(container).toMatchSnapshot();
expect(screen.getByText(pageInfo)).toBeInTheDocument();
const goBackBtn = screen.getByRole("button");
userEvent.click(goBackBtn);
await waitFor(() => {
expect(navigate).toBeCalled();
});
});
});
I try to spyOn the useNavigate hook from react router V6 but when I run the code I got
TypeError: Cannot redefine property: useNavigate
Mocking it should work:
jest.mock('react-router-dom', () => ({
...(jest.requireActual('react-router-dom')), // since you still want to use the actual MemoryRouter
useNavigate: () => useNavigateMock,
}))

Jest, Apollo Client different implementations

Currently I have this working test:
import React from 'react';
import { render } from '#testing-library/react';
import AuditList from '../../../components/Audit';
jest.mock('#apollo/client', () => ({
useLazyQuery: jest.fn().mockReturnValue([
jest.fn(),
{
data: {
someTestingData: 'some Value'
},
loading: false,
error: false
}
])
}));
describe('Audit', () => {
it('should render audit', () => {
const rendered = render(<AuditList />);
const getBytext = rendered.queryByText('my title');
expect(getBytext).toBeTruthy();
});
});
But, I want to test different cases: when ´loading/error´ is true/false.
How can I make specific definitions of the mock for different ´it´?
EDIT: I tried this:
import React from 'react';
import { useLazyQuery } from '#apollo/client';
import { render } from '#testing-library/react';
import AuditList from '../../../components/communications/Audit';
jest.mock('#apollo/client');
describe('Audit', () => {
it('should render audit', () => {
useLazyQuery.mockImplementation(() => [
jest.fn(),
{
data: {
someTestingData: 'some Value'
},
loading: false,
error: false
}
]);
const rendered = render(<AuditList />);
const getBytext = rendered.queryByText('Auditoría');
expect(getBytext).toBeTruthy();
});
});
But I get ´ Cannot read property 'mockImplementation' of undefined ´

Testing with Jest while using MSW and RTK Query leads to strange error in test

I have been spending most of the day trying to sort out this insanely annoying bug.
I am using redux-toolkit, MSW, RTK query, and React Testing Libary and am currently busy writing an integration test that tests a simple login flow.
The problem I have is that I am testing two different scenarios in one test suite, one is a successful login and one is a failed one.
When I run one at a time, I get no problems, but when when I run both, I get the following error for the failed scenario.
TypeError: Cannot convert undefined or null to object
at Function.values (<anonymous>)
59 | (state, action) => {
60 | const { payload } = action;
> 61 | adapter.upsertMany(state, payload);
| ^
62 | }
63 | );
64 | },
at ensureEntitiesArray (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:794:27)
at splitAddedUpdatedEntities (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:799:19)
at upsertManyMutably (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:911:18)
at runMutator (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:772:17)
at Object.upsertMany (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:776:13)
at src/features/customers/store/customersSlice.ts:61:17
at recipe (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:663:32)
at Immer.produce (node_modules/immer/src/core/immerClass.ts:94:14)
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:662:54
at Array.reduce (<anonymous>)
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:641:29
at combination (node_modules/redux/lib/redux.js:536:29)
at dispatch (node_modules/redux/lib/redux.js:296:22)
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1366:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1264:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1224:22
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1138:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1087:22
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1049:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1424:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1458:24
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:446:22
at node_modules/redux-thunk/lib/index.js:14:16
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:374:36
at dispatch (node_modules/redux/lib/redux.js:667:28)
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:1204:37
at step (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:38:23)
at Object.next (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:19:53)
at fulfilled (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:97:32)
What makes this strange is that the failed scenario isn't supposed to get to the page that calls the API call that results in this extra reducer matcher, hence why there is no payload and the error happens.
This doesn't happen when I test in the browser, only when testing with Jest.
Below are my tests:
import React from "react";
import { render, screen, waitFor, cleanup } from "./test-utils";
import App from "../App";
import userEvent from "#testing-library/user-event";
import { waitForElementToBeRemoved } from "#testing-library/react";
import { configureStore } from "#reduxjs/toolkit";
import { api } from "../services/api/api";
import counterReducer from "../features/counter/counterSlice";
import customersReducer from "../features/customers/store/customersSlice";
import subscriptionsReducer from "../features/subscriptions/store/subscriptionsSlice";
import uiReducer from "../features/common/store/uiSlice";
import authReducer from "../features/auth/store/authSlice";
describe("LoginIntegrationTests", () => {
afterEach(() => {
cleanup();
});
it("should render the correct initial state", function () {
render(<App />);
// it doesnt render an appbar
let navbar = screen.queryByRole("heading", {
name: /fincon admin console/i,
});
expect(navbar).not.toBeInTheDocument();
// it renders an empty email address field
const emailField = screen.getByLabelText(/email address/i);
expect(emailField).toHaveTextContent("");
// it renders an empty password password field and hides the input
const passwordField = screen.getByLabelText(/password/i);
expect(passwordField).toHaveTextContent("");
expect(passwordField).toHaveAttribute("type", "password");
// it renders a disabled login button
const loginButton = screen.getByRole("button", { name: /login/i });
emailField.focus();
expect(loginButton).toBeDisabled();
});
it("should complete a successful login flow", async function () {
render(<App />);
// it fills out the email address and password
const emailField = screen.getByLabelText(/email address/i);
const passwordField = screen.getByLabelText(/password/i);
await userEvent.type(emailField, "joe#soap.co.za");
await userEvent.type(passwordField, "blabla");
// it clicks the login button
const loginButton = screen.getByRole("button");
expect(loginButton).toHaveTextContent(/login/i);
userEvent.click(loginButton);
// it sets the loading state
expect(loginButton).toBeDisabled();
expect(loginButton).toHaveTextContent(/loading .../i);
const loadingSpinner = document.querySelector(".k-loading-mask");
expect(loadingSpinner).toBeInTheDocument();
// it removes the previous page's components
await waitFor(() => {
expect(emailField).not.toBeInTheDocument();
expect(passwordField).not.toBeInTheDocument();
expect(loginButton).not.toBeInTheDocument();
expect(loadingSpinner).not.toBeInTheDocument();
});
// it navigates to the customers page
const accountsPage = screen.getByRole("heading", { name: /accounts/i });
expect(accountsPage).toBeInTheDocument();
// it displays the appbar
const navbar = screen.getByRole("heading", {
name: /fincon admin console/i,
});
expect(navbar).toBeInTheDocument();
});
it("should present an error when invalid credentials are entered", async function () {
render(<App />);
// it fills in invalid credentials
const emailField = screen.getByLabelText(/email address/i);
const passwordField = screen.getByLabelText(/password/i);
await userEvent.type(emailField, "error#error.co.za");
await userEvent.type(passwordField, "blabla1");
// it clicks the login button
const loginButton = screen.getByRole("button");
expect(loginButton).toHaveTextContent(/login/i);
userEvent.click(loginButton);
// it sets the loading state
expect(loginButton).toBeDisabled();
expect(loginButton).toHaveTextContent(/loading .../i);
const loadingSpinner = document.querySelector(".k-loading-mask");
expect(loadingSpinner).toBeInTheDocument();
// it removes the loading spinner
await waitForElementToBeRemoved(loadingSpinner);
// it displays the error
const errors = await screen.findByText(
/the provided credentials are invalid/i
);
expect(errors).toBeInTheDocument();
// it stays on the same page
expect(screen.getByText(/log into the admin console/i)).toBeInTheDocument();
// it retains the input of the fields
expect(emailField).toHaveValue("error#error.co.za");
expect(passwordField).toHaveValue("blabla1");
});
});
Below is my redux setup for the tests:
import React from "react";
import { render as rtlRender } from "#testing-library/react";
import { configureStore } from "#reduxjs/toolkit";
import { Provider, useDispatch } from "react-redux";
import { Router } from "react-router-dom";
import { createMemoryHistory } from "history";
import { reducer, store } from "../app/store";
import { api } from "../services/api/api";
import { setupListeners } from "#reduxjs/toolkit/query";
import { renderHook } from "#testing-library/react-hooks";
import counterReducer from "../features/counter/counterSlice";
import customersReducer from "../features/customers/store/customersSlice";
import subscriptionsReducer from "../features/subscriptions/store/subscriptionsSlice";
import uiReducer from "../features/common/store/uiSlice";
import authReducer from "../features/auth/store/authSlice";
// import { useAppDispatch } from "../app/hooks";
function render(
ui,
{
preloadedState,
store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
counter: counterReducer,
customers: customersReducer,
subscriptions: subscriptionsReducer,
ui: uiReducer,
auth: authReducer,
},
preloadedState,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
}),
...renderOptions
} = {}
) {
setupListeners(store.dispatch);
function Wrapper({ children }) {
const history = createMemoryHistory();
return (
<Provider store={store}>
<Router history={history}>{children}</Router>
</Provider>
);
}
// function useAppDispatch() {
// return useDispatch();
// }
// type AppDispatch = typeof store.dispatch;
// const useAppDispatch = () => useDispatch<AppDispatch>();
store.dispatch(api.util.resetApiState());
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}
export * from "#testing-library/react";
export { render };
Below is my setupTests.ts file.
import "#testing-library/jest-dom/extend-expect";
import { server } from "./mocks/server";
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => {
server.resetHandlers();
});
And finally my MSW files.
handlers
import { rest } from "msw";
import { authResponse } from "./data";
import { customers } from "../utils/dummyData";
import { LoginRequest } from "../app/types/users";
import { ApiFailResponse } from "../app/types/api";
export const handlers = [
rest.post("/login", (req, res, ctx) => {
const body = req.body as LoginRequest;
if (body.emailAddress === "error#error.co.za") {
const response: ApiFailResponse = {
errors: ["The provided credentials are invalid"],
};
return res(ctx.status(400), ctx.json(response));
} else {
return res(ctx.json(authResponse));
}
}),
rest.get("/customers", (req, res, ctx) => {
return res(ctx.json(customers));
}),
];
server
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
Any ideas?
Thanks for all your help!
You should probably also reset the api between the tests, as the api has internal state as well.
Call
afterEach(() => {
store.dispatch(api.util.resetApiState())
})
For reference, this is how RTK Query internally sets up the tests: https://github.com/reduxjs/redux-toolkit/blob/4fbd29f0032f1ebb9e2e621ab48bbff5266e312c/packages/toolkit/src/query/tests/helpers.tsx#L115-L169
This was due to a bug in my app that appears in edge cases, as #phry correctly guessed.

mapDispatchToProps props null in jest test

I have just started my first react job (yay!) and am attempting to write unit tests using jest. i am having an issue with mapDispatchToProps in my test.
please note, the test passes, but it is throwing the following error message:
Warning: Failed prop type: The prop testDispatch is marked as required in TestJest, but its value is undefined in TestJest
error seems to be test related to the test as it is not thrown when running the page in the web browser. also, mapStateToProps works fine, included to show it works.
using enzyme/shallow in order not to interact with the <Child/> component.
testJest.js
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import Child from './Child'
export class TestJest extends React.Component {
render() {
return (<div>ABC - <Child /></div>);
}
}
TestJest.propTypes = {
testDispatch: PropTypes.func.isRequired,
testState: PropTypes.arrayOf(PropTypes.string)
};
const mapStateToProps = (state) => {
return {
testState: state.utilization.pets // ['dog','cat']
};
};
const mapDispatchToProps = (dispatch) => {
return {
testDispatch: () => dispatch({'a' : 'abc'})
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TestJest);
testJest-test.js
import React from 'react';
import { TestJest } from '../Components/testJest';
import TestJestSub2 from '../Components/TestJestSub2';
import { shallow } from 'enzyme';
describe('TESTJEST', () => {
it('matches snapshot', () => {
const wrapper = shallow(<TestJest />);
expect(wrapper).toMatchSnapshot();
});
});
You can pass empty function to TestJest as testDispatch prop:
it('matches snapshot', () => {
const wrapper = shallow(<TestJest testDispatch={() => {}} />);
expect(wrapper).toMatchSnapshot();
});
or pass jest mock function:
it('matches snapshot', () => {
const wrapper = shallow(<TestJest testDispatch={jest.fn()} />);
expect(wrapper).toMatchSnapshot();
});
if you want to check if testDispatch was called (expect(wrapper.instance().props.testDispatch).toBeCalled();)

Resources