I have appRoutes.js file
import projectConfig from './projectConfig'
import React, {lazy} from 'react'
const Busin = lazy(() => import('./busine'))
const own = lazy(() => import(()=>import('./own'))
export const appRoutes = [{
path: projectConfig.route, component: Busin},
{path: projectConfig.route, component: own}]
own.js
import React from 'react'
const own = () => {
return <div>
<form>some child component</form>
</div>
}
export default own
appRoute.test.js
import {render, waitFor} from '#testing-library/react'
describe('test', () => {
it('lazy', () => {
const {getByText} = render(<appRoutes />)
await waitfor(() => {
expect(getByText('').tobeinthedocument()
})
})
})
How can I cover the lazy load component here in the test coverage
Looks like you are re-assigning the container returned by render, I think your test should be:
import React from 'react'
import {render, waitFor, getByText } from 'react-testing-library'
import AppRoutes from 'AppRoutes'
test('renders lazy component', async () => {
const { container } = render(<appRoutes />)
await waitFor(() => expect(getByText(container, 'I am lazy !' )).toBeInTheDocument())
})
Related
My component code is as below. Not an expert in Jest mocking. referred How to mock useHistory hook in jest? and mocked useHistory.push. But the mock function is not being hit. I would appreciate any suggestions
const ReviseAction = ({
plans,
template,
coveragePercentage,
territoryName,
existingTemplateId,
}) => {
const history = useHistory();
const handleRevise = () => {
history.push({
pathname: "/xxx",
state: {
plans: plans,
template: template,
coveragePercentage: coveragePercentage,
territoryName: territoryName,
existingTemplateId: existingTemplateId,
},
});
};
return (
<button
data-testid="revise-button"
onClick={handleRevise}
key="revise-button"
>
Revise
</button>
);
};
Here is my test:
import React from "react";
import { render, screen, fireEvent } from "#testing-library/react";
import ReviseAction from "./ReviseAction";
import { HashRouter as Router } from "react-router-dom";
describe("ReviseAction", () => {
jest.mock("react-router-dom");
const pushMock = jest.fn();
//reactRouterDom.useHistory = jest.fn().mockReturnValue({push: pushMock});
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useHistory: () => ({
push: jest.fn()
})
}));
it("Renders component", async () => {
render(
<Router>
<ReviseAction
plans={[]}
template={{}}
coveragePercentage={"12"}
territoryName={"Name"}
existingTemplateId={"1234"}
/>
</Router>
);
fireEvent.click(screen.queryByTestId("revise-button"));
expect(pushMock).toHaveBeenCalled();
});
});
and getting
Expected number of calls: >= 1
Received number of calls: 0
This fixed it.
What I did:
Changed HashRouter import to router
passed the mock history as props to router.
import React from "react";
import { render, screen, fireEvent } from "#testing-library/react";
import ReviseAction from "./ReviseAction";
import { Router } from "react-router-dom";
describe("ReviseAction", () => {
const mockPush = jest.fn();
jest.mock("react-router-dom", () => ({
useHistory: () => ({
push: mockPush,
}),
}));
const mockHistory = { push: mockPush, location: {}, listen: jest.fn() };
it("Renders component", async () => {
render(
<Router history={mockHistory}>
<ReviseAction
plans={[]}
template={{}}
coveragePercentage={"12"}
territoryName={"Name"}
existingTemplateId={"1234"}
/>
</Router>
);
fireEvent.click(screen.queryByTestId("revise-button"));
expect(mockPush).toHaveBeenCalled();
});
});
I am using context that updates my snackbar content from a single place. So whenever I want to display snackbar in some other component as a consumer, I update its value. Everything is functionally working but my tests are failing.
I am using mui snackbar.
For the reference: I am using react testing library coupled with jest.
Note: What I have done already:
waitFor with timeout property
Wrapped ContextProvider in render function
This is my code:
HOME.JS
import React from 'react';
import {Suspense} from 'react';
import {useState} from 'react';
import {Route} from 'react-router-dom';
import {Switch} from 'react-router-dom';
import {useLocation} from 'react-router-dom';
import withStyles from '#mui/styles/withStyles';
import BillingDetails from 'views/BillingDetails/BillingDetails';
import ErrorBoundary from 'containers/ErrorBoundary/ErrorBoundary';
import PageNotFound from 'views/PageNotFound/PageNotFound';
import SideBar from 'components/SideBar/SideBar';
import Snackbar from 'components/CustomSnackbar/CustomSnackbar';
import Unauthorized from 'views/Unauthorized/Unauthorized';
import {subRoutesConfig} from 'constants/routeConfig';
import {styles} from './styles';
import {BILLING_OVERDUE} from 'views/BillingDetails/constants';
import SidebarJobsContext from 'context/sidebarJobsContext';
import SnackbarContext from 'context/snackbarContext';
import {PropTypes} from 'prop-types';
const useStyles = (theme) => ({
...styles(theme),
});
const Home = (props) => {
let location = useLocation();
const [sidebarJobs, setSidebarJobs] = useState([]);
const [updateJobs, setUpdateJobs] = useState(false);
const [snackbar, setSnackbar] = useState({});
const sidebarJobsContextValue = {sidebarJobs, setSidebarJobs, updateJobs, setUpdateJobs};
const snackbarContextValue = {snackbar, setSnackbar};
const handleSnackbarClose = (reason, onClose) => {
if (reason == 'clickaway') {
return;
}
onClose && onClose();
setSnackbar({...snackbar, open: false});
};
const showSnackbar = () => {
const {
autoHideDuration,
text,
severity,
onExternalLink,
open=false,
onClose,
handleUndo,
} = snackbar;
return <Snackbar
autoHideDuration={autoHideDuration}
handleClose={(event, reason) => handleSnackbarClose(reason, onClose)}
handleExternalLink={onExternalLink}
handleUndo={handleUndo}
open={open}
severity={severity}
text={text}
/>;
};
const {
classes,
} = props;
const currentURLPath = location.pathname;
return (
<>
<SidebarJobsContext.Provider value={sidebarJobsContextValue}>
<SnackbarContext.Provider value={snackbarContextValue}>
<SideBar/>
<div className={classes.root}>
<div className={classes.offset}>
<ErrorBoundary key={currentURLPath}>
<Suspense fallback={<span></span>}>
<Switch>
{
(redirect || billingInfo.subscription_expiry) && <Route>
<BillingDetails/>
</Route>
}
{subRoutesConfig.map((route, i) => {
let component = isRouteAllowed(route) ? route.component : Unauthorized;
return (
<Route
component={component}
exact={route.exact}
key={route}
path={route.path}
/>
);
})}
<Route component={PageNotFound} />
</Switch>
{showProductTour()}
{showSnackbar()}
</Suspense>
</ErrorBoundary>
</div>
</div>
</SnackbarContext.Provider>
</SidebarJobsContext.Provider>
</>
);
};
Home.propTypes = {
classes: PropTypes.object,
};
export default withStyles(useStyles)(Home);
As you can see this is the Home.js component where I have added the provider and showSnackbar() takes care of displaying snackbar.
and then I am using useContext in functional and contextType in class components.
There are many tests that are failing here's an example of one:
import React from 'react';
import {fireEvent} from '#testing-library/react';
import {render} from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import {waitFor} from '#testing-library/react';
import {convertIntoRGB} from 'utilities/utils';
import {ThemeWrapper} from 'utilities/testUtilities';
import {error} from 'constants/colors';
import {success} from 'constants/colors';
import CompanySettings from '../CompanySettings';
import SnackbarContext from '../../app/src/context/snackbarContext';
let snackbarContextValue = {
snackbar: {
text: '',
severity: 'success',
open: false,
autoHideDuration: 3000,
handleUndo: () => {},
onClose: () => {},
},
setSnackbar: () => {},
};
const component = (props) => {
return (render(<ThemeWrapper
storeIncluded={true}>
<SnackbarContext.Provider value={snackbarContextValue}>
<CompanySettings />
</SnackbarContext.Provider>
</ThemeWrapper>));
};
describe('CompanySettings', () => {
test('should Render success snackbar', async () => {
const {getByText, getByTestId, container, queryAllByTestId} = component();
expect(getByText('Company logo')).toBeInTheDocument();
expect(getByTestId('custom-icon-Package')).toBeVisible();
expect(queryAllByTestId('custom-skeleton').length).toBe(8);
await waitFor(()=> {
expect(queryAllByTestId('custom-skeleton').length).toBe(4);
let editBtn = getByText(/Edit company profile/i);
fireEvent.click(editBtn);
const inputField = container.querySelector(`input[name=companyName]`);
userEvent.type(inputField, ' 1');
fireEvent.click(getByTestId('saveBtn'));
});
await waitFor(() => {
expect(getByText('Gomez Inc 1')).toBeInTheDocument();
expect(container.querySelector(`input[name=companyName]`)).not.toBeInTheDocument();
});
await waitFor(() => {
expect(getByText(`Changes saved to 'company profile'.`)).toBeInTheDocument();
expect(getComputedStyle(getByTestId('alert')).backgroundColor).toEqual(convertIntoRGB(success));
});
});
});
The last waitFor in this test is failing as I have used context to display this snackbar content that says Changes saved to 'company profile'.
In our Next.js app we use vinissimus/next-translate package for translations.
We set it up accordingly:
next.config.js:
const nextTranslate = require('next-translate')
module.exports = nextTranslate()
i18n.js config file:
{
"locales": ["en"],
"defaultLocale": "en",
"pages": {
"*": ["en"],
"/": ["en"],
}
}
Using it inside the component with useTranslation hook:
const App = () => {
const { t, lang } = useTranslation();
return (
<Homepage>
<span>{t(`${lang}:homepage.title.header`)}</span>
</Homepage>
My question is how can I test it with jest with react-testing-library?
When I'm rendering my component in tests the translations do not work, only the key is returned. Also, I'd like to test it by getByText / findByText and passing:
{t(`${lang}:homepage.header`)}
How can I setup some kind of a wrapper or config for tests if I'm not using any i18nProvider in app but only this i18n.js config?
Here is an example wrapper:
// tests/wrapper.js
import { render } from '#testing-library/react'
import I18nProvider from 'next-translate/I18nProvider'
import commonEN from '../locales/en/common.json'
const Providers = ({ children }) => (
<I18nProvider
lang={'en'}
namespaces={{
common: commonEN
}}
>
{children}
</I18nProvider>
)
const customRender = (ui, options = {}) => render(ui, { wrapper: Providers, ...options })
export * from '#testing-library/react'
export { customRender as render }
A test would look like
// App.spec.js
import React from 'react'
import { render } from './tests/wrapper'
import App from './App'
jest.mock('next/router', () => ({
useRouter() {
return {
asPath: '/'
}
}
}))
describe('App', () => {
it('should render the app', async () => {
const res = render(<App />)
})
})
I wan't to check if history.push() has been called with the correct parameters in my test.
I'm not sure what's the correct way to mock useHistory()
I tried this solution. But it seems that I can't check if push() has been called.
App.tsx
import React from 'react';
import {useHistory} from 'react-router-dom';
const App: React.FC = () => {
const history = useHistory();
const onClick = () => {
history.push('/anotherPath');
};
return (
<div>
<button onClick={onClick}>click</button>
</div>
);
};
export default App;
App.test.tsx
import React from 'react';
import {render, fireEvent} from '#testing-library/react';
import App from './App';
import {useHistory} from 'react-router-dom'
jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: jest.fn(),
}),
}));
test('renders learn react link', async () => {
const app = render(<App/>);
fireEvent.click(app.getByText('click'));
expect(useHistory().push).toBeCalledWith('/anotherPath');
});
Is there any way to make sure that history.push() has been called with the correct parameters?
Try this, assign the mocked push method into a variable and use that to assert if it is called with the right parameters.
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import { useHistory } from "react-router-dom";
const mockHistoryPush = jest.fn();
const App: React.FC = () => {
const history = useHistory();
const onClick = () => {
history.push("/anotherPath");
};
return (
<div>
<button onClick={onClick}>click</button>
</div>
);
};
jest.mock("react-router-dom", () => ({
useHistory: () => ({
push: mockHistoryPush
})
}));
test("renders learn react link", async () => {
const { getByText } = render(<App />);
fireEvent.click(getByText("click"));
expect(mockHistoryPush).toBeCalledWith("/anotherPath");
});
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);
})