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?
Related
Learning unit-testing on the react with typescript, encountered an error when tests fall when importing axios.
screenshot error in the terminal](https://i.stack.imgur.com/dFxJU.png)
Code component
import axios from "axios";
import React, { FC, useEffect, useState } from "react";
import { IUser } from "../../types/IUsers";
const Users: FC = () => {
const [users, setUsers] = useState<IUser[]>([]);
useEffect(() => {
getUsers();
}, [users]);
const getUsers = async () => {
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/users/"
);
const res = response.data;
setUsers(res);
} catch (error) {
console.log(error);
}
};
return (
<div data-testid="users-wrapper">
{users.map((user) => (
<div>{user.name}</div>
))}
</div>
);
};
export default Users;
Code test
import React from "react";
import { render, screen } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import Users from "./Users";
import axios from "axios";
jest.mock("axios");
describe("Testing user component", () => {
test("Show title", () => {
render(<Users />);
const usersWrapper = screen.getByTestId("users-wrapper");
expect(usersWrapper).toBeInTheDocument();
});
});
Tried install types for axios, create babel-config, create .babelrc, add `
--transformIgnorePatterns \"node_modules/(?!axios)/\""
` on the package-json. Help me please.
Add the following in package.json and try:
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!axios)/"
]
},
This fixed the issue for me.
I use this pattern, and it's works for me too
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.
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
So i have this axios test and Im getting an empty div, not sure why.
test
import React from 'react';
import ReactDOM from 'react-dom';
import TestAxios from '../test_axios.js';
import {act, render, fireEvent, cleanup, waitForElement} from '#testing-library/react';
import axiosMock from "axios";
afterEach(cleanup)
it('Async axios request works', async () => {
const url = 'https://jsonplaceholder.typicode.com/posts/1'
const { getByText, getByTestId } = render(<TestAxios url={url} />);
act(() => {
axiosMock.get.mockImplementation(() => Promise.resolve({ data: {title: 'some title'} })
.then(console.log('ggg')) )
})
expect(getByText(/...Loading/i).textContent).toBe("...Loading")
const resolvedSpan = await waitForElement(() => getByTestId("title"));
expect((resolvedSpan).textContent).toBe("some title");
expect(axiosMock.get).toHaveBeenCalledTimes(1);
expect(axiosMock.get).toHaveBeenCalledWith(url);
})
the component
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const TestAxios = (props) => {
const [state, setState] = useState()
useEffect(() => {
axios.get(props.url)
.then(res => setState(res.data))
}, [])
return (
<div>
<h1> Test Axios Request </h1>
{state
? <p data-testid="title">{state.title}</p>
: <p>...Loading</p>}
</div>
)
}
export default TestAxios;
the mock function
export default {
get: jest.fn().mockImplementation(() => Promise.resolve({ data: {} }) )
};
so Im supposed to get a p element with some text but I get nothing. I have tried many different things bt cant seem to get it work not sure why its not working
So I figured it out it turns out you have to call axios.mockresolved value before the rendering of the component, otherwise it will just use the value you provided as the default in your mock axios module.
import React from 'react';
import ReactDOM from 'react-dom';
import TestAxios from '../test_axios.js';
import {act, render, fireEvent, cleanup, waitForElement} from '#testing-library/react';
import axiosMock from "axios";
afterEach(cleanup)
it('Async axios request works', async () => {
axiosMock.get.mockResolvedValue({data: { title: 'some title' } })
const url = 'https://jsonplaceholder.typicode.com/posts/1'
const { getByText, getByTestId, rerender } = render(<TestAxios url={url} />);
expect(getByText(/...Loading/i).textContent).toBe("...Loading")
const resolvedEl = await waitForElement(() => getByTestId("title"));
expect((resolvedEl).textContent).toBe("some title")
expect(axiosMock.get).toHaveBeenCalledTimes(1);
expect(axiosMock.get).toHaveBeenCalledWith(url);
})
I am just trying to figure out how to do tests on components that are wrapped with connect. How do I properly define the redux state prop to my component?
● PendingContract with connect/Redux › +++ render the connected(SMART) component
TypeError: Cannot read property 'find' of undefined
Original Component code:
// Dependencies
import React, { Component } from 'react';
import CSSModules from 'react-css-modules';
import { connect } from 'react-redux';
import * as actions from '../../../../actions';
import PendingContractDetail from './pending-contract-
detail/PendingContractDetail';
// CSS
import styles from './PendingContract.css';
export class PendingContract extends Component {
componentWillMount() {
this.props.getSinglePendingContract(this.props.params.contract);
}
render() {
let contract;
if (this.props.contract) {
const contractDetails = this.props.contract;
contract = (
<PendingContractDetail
accepted={contractDetails.accepted}
contractId={contractDetails.contractId}
contractName={contractDetails.contractName}
details={contractDetails.details}
status={contractDetails.status}
timeCreated={contractDetails.timeCreated}
type={contractDetails.type} />
);
} else {
contract = 'Loading...'
};
return (
<div className='row'>
<div className='col-xs-12 col-sm-12 col-md-12'>
{contract}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
contract: state.pendingContracts.contract
}
}
const PendingContractWithCSS = CSSModules(PendingContract, styles);
export default connect(mapStateToProps, actions)(PendingContractWithCSS);
Test Code as follows:
import React from 'react';
import reduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { shallow, mount } from 'enzyme';
import PendingContract from './PendingContract';
import configureStore from 'redux-mock-store';
jest.mock('react-css-modules', () => Component => Component);
describe('PendingContract with connect/Redux', () => {
const initialState = {
contract: {
accepted: true,
contractId: 1234,
contractName: 'Test Contract',
details: { test: 'test'},
status: 'Accepted',
type: 'Sports'
}
};
const mockStore = configureStore([reduxThunk])
let store,wrapper;
beforeEach(()=>{
store = mockStore(initialState)
wrapper = mount(<Provider store={store}><PendingContract {...initialState} /></Provider>)
})
it('+++ render the connected(SMART) component', () => {
expect(wrapper.find(PendingContract).length).toEqual(1)
});
// it('+++ check Prop matches with initialState', () => {
// expect(wrapper.find(PendingContract).prop('contract')).toEqual(initialState.contract)
// });
});
You need to import connected component if you are trying to fully test it with mount:
import React from 'react';
import reduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { shallow, mount } from 'enzyme';
import ConnectedPendingContract, { PendingContract } from './PendingContract';
import configureStore from 'redux-mock-store';
jest.mock('react-css-modules', () => Component => Component);
describe('PendingContract with connect/Redux', () => {
const initialState = {
contract: {
accepted: true,
contractId: 1234,
contractName: 'Test Contract',
details: { test: 'test'},
status: 'Accepted',
type: 'Sports'
}
};
const mockStore = configureStore([reduxThunk])
let store,wrapper;
beforeEach(()=>{
store = mockStore(initialState)
wrapper = mount(<Provider store={store}><ConnectedPendingContract {...initialState} /></Provider>)
})
it('+++ render the connected(SMART) component', () => {
expect(wrapper.find(PendingContract).length).toEqual(1)
});
// it('+++ check Prop matches with initialState', () => {
// expect(wrapper.find(PendingContract).prop('contract')).toEqual(initialState.contract)
// });
});