JEST react-testing-library Cannot read property 'push' of undefined - jestjs

import React from 'react';
import { Provider } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { store } from './store';
import Routes from './router/router';
const App: React.FC<RouteComponentProps> = () => {
return (
<Provider store={store}>
<Routes />
</Provider>
);
};
export default withRouter(App);
function renderWithRouterAndStore(
ui,
{ route = '/', history = createMemoryHistory({ initialEntries: [route] }) } = {}
) {
const Wrapper = ({ children }) => {
return (
<Provider store={store}>
<IntlProvider locale="en">
<MemoryRouter initialEntries={[route]}>
<Route path="/login">{children}</Route>
</MemoryRouter>
</IntlProvider>
</Provider>
);
};
return {
...render(ui, { wrapper: Wrapper }),
history,
};
}
I have Login Component, when i submit the form and if the login is successfully, it navigates to '/'.
I am using this.props.history.push('/')
But my test case fail, Cannot read property 'push' of undefined
How to test a React component with RouteComponentProps?

I am not sure if this is a solution to this problem but you can mock history.push like this:
let props = {
history: historyMock
};
shallow(<LoginComponent {...props} />);

Related

useContext auth hook returning null

I have created a useAuth hook in order to allow the user access to the private route once authorised the authorising cookie is stored properly but the useAuth hook seems to be returning null. I also tried printing the value of setUser but it doesn't seem to print after the line setUser(res.data.currentUser) but the one before is printed I cannot figure out why.
The cookies are stored and set properly it just seems to be the auth hook that return setUser as null even though the line console.log("RES>DATA = ", res.data.currentUser); logs the correct details of the cookie but when I try to manually change the url to the private route after the cookie is stored it returns a null for the setUser returned from the auth hook and so access to private route isn't granted. This is done using react for frontend with node js and express for the backend. If any more context is necessary please let me know.
useAuth hook:
import { useState, useContext, useEffect } from "react";
import { UserContext } from "./UserContext";
import axios from "axios";
export default function useAuth() {
const { setUser } = useContext(UserContext);
const [error, setError] = useState(null);
useEffect(() => {
console.log("USE effect");
setUserContext();
});
//set user in context and push them home
const setUserContext = async () => {
console.log("In here");
return await axios
.get("auth/checkAuth")
.then((res) => {
console.log("RES>DATA = ", res.data.currentUser); // PRINTS THE CORRECT COOKIE VALUES
setUser(res.data.currentUser); // THIS SEEMS TO BE NULL?????
console.log("SET USER + ", setUser); // THIS DOES NOT PRINT
})
.catch((err) => {
setError(err);
});
};
return {
setUser,
};
}
Private route:
import React, { useContext } from 'react';
import {Route, Redirect, Navigate} from 'react-router-dom';
import { UserContext } from '../hooks/UserContext';
import useAuth from '../hooks/useAuth';
export default function PrivateRoute({children}) {
const auth = useAuth();
console.log("aUTH in priv = ", auth);
return auth ? children : <Navigate to="/"/>
}
App.js:
import "./App.css";
import { Redirect, Route, Routes, Switch } from "react-router-dom";
import useFetch from "./useFetch";
import { useEffect, useState, useRef } from "react";
import axios from "axios";
import Activities from "./components/Activities";
import HomePage from "./components/Home";
import Map from "./components/Map";
import checkUser from "./hooks/checkUser";
import { UserContext } from "./hooks/UserContext";
import useFindUser from "./hooks/checkUser";
import PrivateRoute from "./components/PrivateRoute";
function App() {
const [auth, setAuth] = useState(false);
const [activities, setActivities] = useState([]);
const notInitialRender = useRef(false);
const { user, setUser, isLoading } = useFindUser(); // works as expected
return (
<div className="App">
<UserContext.Provider value={{ user, setUser, isLoading }}>
<Routes>
<Route path="/" element={<HomePage />}></Route>
<Route path="/Activities" element={<Activities />} />
<Route
path="/Private"
element={
<PrivateRoute>
<Map />
</PrivateRoute>
}
/>
</Routes>
</UserContext.Provider>
</div>
);
}
export default App;
auth/checkAuth express route:
export const checkAuth = (req, res) => {
let currentUser;
console.log("res.cookies = ", req);
if (req.cookies.currentUser) {
// res.send(200);
currentUser = req.cookies.currentUser; // set to user object in cookies
console.log("current user = ", currentUser);
// return 200 status code
} else {
currentUser = null;
}
//res.json(currentUser);
res.status(200).send({ currentUser });
};
Map component which is private route component:
import react from 'react';
function Map() {
<div>
<p style={{color:'red'}}>You have access to the private route</p>
</div>
}
export default Map;
UserContext file:
import { createContext } from "react";
export const UserContext = createContext("null");
I tried logging values and using useEffect to call the function when the useAuth hook is called but couldn't figure it out

Authentication reactjs website with Firebase using email and password

I tried to create an authentication website with Firebase using email and password. I can't even load the Login page.
Here's Auth.js
import React, { useState, useEffect} from "react";
import { auth } from './config'
import { onAuthStateChanged } from "firebase/auth";
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
})
}, [])
return (
<AuthContext.Provider value={{currentUser}}>
{children}
</AuthContext.Provider>
)
}
And this is Login.js
import React, {useContext ,useState } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import "./Login.css";
import { auth } from './config'
import { signInWithEmailAndPassword } from "firebase/auth";
import { AuthContext } from "./Auth";
import { useHistory } from "react-router-dom";
const Login = () => {
let history = useHistory();
const handleSubmit = (event) => {
event.preventDefault();
const { email, password } = event.target.elements;
signInWithEmailAndPassword(auth, email.value, password.value)
.then((userCredential) => {
const user = userCredential.user;
console.log(user.uid);
})
.catch((error) => {
console.log(error.massage);
});
}
const currentUser = useContext(AuthContext);
if(currentUser) {
return history.push('/dashboard');
}
return (
<div className="Login">
<h1>Login</h1>
<Form onSubmit={handleSubmit}>
//Login Form
</Form>
</div>
);
}
export default Login
And DashBoard.js
import React, {useContext} from 'react'
import { AuthContext } from './Auth'
import { auth } from './config'
import { signOut } from 'firebase/auth'
import { useHistory } from "react-router-dom";
const DashBoard = () => {
const currentUser = useContext(AuthContext);
let history = useHistory();
if(!currentUser) {
return history.push('/login');
}
const signOutFunc = () => {
signOut(auth)
}
return (
<div>
<div className='container mt-5'>
<h1>Welcome</h1>
<h2>If you see this you are logged in.</h2>
<button className='btn btn-danger' onClick={signOutFunc}>Sign Out</button>
</div>
</div>
)
}
export default DashBoard;
Lastly App.js
import { BrowserRouter as Router, Route, Switch} from 'react-router-dom'
import Login from './Login'
import DashBoard from './DashBoard';
import { AuthProvider } from './Auth'
function App() {
return (
<AuthProvider>
<Router>
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
</Router>
</AuthProvider>
);
}
export default App;
When I open /login, it would send me to /dasgboard immediately. If I typed /login again it gives me this error
Error: Login(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I can't figure it out what's wrong with it. Please help me.
Thank you
You have multiple places in your code where you return history.push('/dashboard'); or another path. You should return there a null:
if(!currentUser) {
history.push('/login');
return null
}

Is there any way to stop the useEffect infinite loop even after provided with second empty array argument?

I am try to call dispatch method from useEffect hook in my React app but after rendering the useEffect continue looping.
here is my code....
category.actions.js
here is the actions perform
import axios from "../../apiFetch/axios";
import { categoryActions } from './constants';
export const getCategories = () => {
return async (dispatch) => {
dispatch({ type: categoryActions.GET_CATEGORY_ACTION})
const res = await axios.get('/category/getCategories');
console.log(res)
if(res.status === 200){
const {categoryList} = res.data;
dispatch({
type: categoryActions.GET_CATEGORY_SUCCESS,
payload: {
categories: categoryList
}
});
}else{
dispatch({
type: categoryActions.GET_CATEGORY_FAILURE,
payload: {
error: res.data.error
}
});
}
}
}
category.reducer.js
this is reducer code
import { categoryActions } from "../actions/constants";
const initialState = {
laoding: false,
categoryList: [],
error: null
}
const categoryReducer = (state = initialState, action) => {
switch(action.type){
case categoryActions.GET_CATEGORY_ACTION:
return{
loading: true
};
case categoryActions.GET_CATEGORY_SUCCESS:
return{
loading: false,
categoryList: action.payload.categories
};
case categoryActions.GET_CATEGORY_FAILURE:
return{
loading: false,
error: action.payload.error
}
default:
return{
...initialState
}
}
}
export default categoryReducer;
Category components where I used useEffect hooks
import Layout from '../../components/Layout';
import {Container, Row, Col} from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux';
import { getCategories } from '../../redux/actions';
const Category = () => {
const dispatch = useDispatch();
const state = useSelector(state => state.categoryReducer)
useEffect(() => {
dispatch(getCategories());
},[]);
return (
<Layout sidebar>
<Container>
<Row>
<Col>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<h3>Category</h3>
<button>Add</button>
</div>
</Col>
</Row>
</Container>
</Layout>
)
}
export default Category;
constant.js
here is the constant i used as type
export const categoryActions = {
GET_CATEGORY_ACTION: 'GET_CATEGORY_ACTION',
GET_CATEGORY_SUCCESS: 'GET_CATEGORY_SUCCESS',
GET_CATEGORY_FAILURE: 'GET_CATEGORY_FAILURE'
}
here is index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
import store from './redux/store';
import { BrowserRouter as Router } from 'react-router-dom';
window.store = store;
ReactDOM.render(
<Provider store={store}>
<Router>
<React.StrictMode>
<App />
</React.StrictMode>
</Router>
</Provider>,
document.getElementById('root')
);
reportWebVitals();
here is App.js parent component
import Signin from './containers/Signin';
import Signup from './containers/Signup';
import PrivateRoute from './components/HOC/PrivateRoute';
import {useDispatch, useSelector} from 'react-redux';
import { isUserLogin } from './redux/actions';
import Products from './containers/Products';
import Orders from './containers/Orders';
import Category from './containers/Category';
function App() {
const auth = useSelector(state => state.authReducer);
const dispatch = useDispatch();
useEffect(() => {
if(!auth.authenticate){
dispatch(isUserLogin());
}
}, [])
return (
<div className="App">
<Switch>
<PrivateRoute path="/" exact component={Home} />
<PrivateRoute path="/products" component={Products} />
<PrivateRoute path="/orders" component={Orders} />
<PrivateRoute path="/category" component={Category} />
<Route path="/signup" component={Signup} />
<Route path="/signin" component={Signin} />
</Switch>
</div>
);
}
export default App;

Testing a Next.js app with a custom /pages/_app.js in React-Testing-Library

I'm attempting to follow this guide in Reat-Testing-Library documentation to wrap all the components I want to test. I'm doing this because I need access to the various context providers that are defined in _app.js within the components I'm testing.
This is my /pages/_app.js file:
export class MyApp extends App {
public componentDidMount() {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles && jssStyles.parentNode) {
jssStyles.parentNode.removeChild(jssStyles);
}
}
public render() {
const { Component, pageProps, apolloClient } = this.props;
return (
<Container>
<StateProvider>
<ThemeProvider theme={theme}>
<ApolloProvider client={apolloClient}>
<CssBaseline />
<Component {...pageProps} />
<SignUp />
<Snackbar />
</ApolloProvider>
</ThemeProvider>
</StateProvider>
</Container>
);
}
}
export default withApollo(MyApp);
This is my /utils/testProviders.js file:
class AllTheProvidersWrapped extends App {
public componentDidMount() {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles && jssStyles.parentNode) {
jssStyles.parentNode.removeChild(jssStyles);
}
}
public render() {
const { pageProps, apolloClient, children } = this.props;
return (
<Container>
<StateProvider>
<ThemeProvider theme={theme}>
<ApolloProvider client={apolloClient}>
<CssBaseline />
{React.cloneElement(children, { pageProps })}
<SignUp />
<Snackbar />
</ApolloProvider>
</ThemeProvider>
</StateProvider>
</Container>
);
}
}
const AllTheProviders = withApollo(AllTheProvidersWrapped);
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options });
export * from "react-testing-library";
export { customRender as render };
This is my /jest.config.js file:
module.exports = {
testPathIgnorePatterns: ["<rootDir>/.next/", "<rootDir>/node_modules/"],
moduleDirectories: ["node_modules", "utils", __dirname]
};
And this is an example of a test I'm trying to run:
import React from "react";
import { render, cleanup } from "testProviders";
import OutlinedInput from "./OutlinedInput";
afterEach(cleanup);
const mockProps = {
id: "name",
label: "Name",
fieldStateString: "signUpForm.fields"
};
describe("<OutlinedInput />", (): void => {
it("renders as snapshot", (): void => {
const { asFragment } = render(<OutlinedInput {...mockProps} />, {});
expect(asFragment()).toMatchSnapshot();
});
});
The error message outputted from the test is:
TypeError: Cannot read property 'pathname' of undefined
52 |
53 | const customRender: CustomRender = (ui, options) =>
> 54 | render(ui, { wrapper: AllTheProviders, ...options });
| ^
55 |
56 | // re-export everything
57 | export * from "react-testing-library";
If I had to guess, I'd say that the <Component {...pageProps} /> component in /pages/_app.js is what provides the pathName as part of Next.js's routing.
The examples provided by Next.js don't cover how to do this so I'm hoping someone here might be able to help.

running test using mount creates: Target container is not a DOM element.

the app runs fine but when I run test i.e) 'yarn run test', it shows error: Target container is not a DOM element. I am not importing the file where the reactdom render happens. I'd appreciate your help solving this issue. thanks.
Steps to Reproduce
please look at reproducible demo
Expected Behavior
It runs fine when I run the app using 'yarn start'. Problem only occurs when I run test.
I expected the test to run correctly.
Actual Behavior
Error comes up, when I 'mount' the app.js component.
Invariant Violation: Target container is not a DOM element.
Reproducible Demo
try running 'yarn run test' in the repo, or look at code samples below. thanks
https://github.com/SeunghunSunmoonLee/react-graphql-d3-vx
```
// containers/app/index.test.js
import App from './index.js'
import { Provider } from 'react-redux'
import { push, ConnectedRouter } from 'react-router-redux';
import { ApolloProvider } from 'react-apollo';
import { shallow, mount } from 'enzyme';
import store from '../../store.js'
// import configureStore from '../../../configureStore';
// import createHistory from 'history/createBrowserHistory';
// import { GET_POSTS } from './index.js';
// const initialState = {};
const history = createHistory({ basename: '/' });
// const store = configureStore(initialState, history);
export const client = new ApolloClient({
uri: 'https://fakerql.com/graphql',
});
const asyncFlush = () => new Promise(resolve => setTimeout(resolve, 1000));
// const mocks = [
// {
// request: {
// query: GET_POSTS,
// },
// result: mockData,
// },
// ];
describe('<Histogram/> in Home Page with real data from server', async () => {
let screen;
beforeEach(async () => {
screen = mount(
<ApolloProvider client={client}>
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
</ApolloProvider>
)
screen.find(Provider).prop('store').dispatch(push('/'));
await asyncFlush();
})
it('shuld have postsByMonthSorted in component state', () => {
expect(screen.state().postsByMonthSorted).not.toHaveLength(0);
expect(screen.state().postsByMonthSorted).toHaveLength(12);
})
it('should render svg properly', () => {
expect(screen.find('svg').to.have.lengthOf(4))
})
it('it should be defined', () => {
expect(Histogram).toBeDefined();
});
// it('it should have the .vx-bar class', () => {
// expect(
// HistogramWrapper({
// className: 'test'
// }).prop('className')
// ).toBe('vx-bar test');
// });
})
// containers/app/index.js
import React from 'react'
import { Route, Link } from 'react-router-dom'
import Home from '../home'
import About from '../about'
// <header style={{width: '400px', height: '50px', display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
// <Link to="/">Home</Link>
// <Link to="/about-us">About</Link>
// </header>
class App extends React.Component {
render() {
return (
<div>
<main>
<Route exact path="/" component={Home} />
<Route exact path="/about-us" component={About} />
</main>
</div>
)
}
}
export default App
// src/index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import { ApolloProvider } from 'react-apollo';
// import { ApolloClient } from 'apollo-client';
// import { HttpLink } from 'apollo-link-http';
// import { InMemoryCache } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-boost';
import store, { history } from './store'
import App from './containers/app'
import 'sanitize.css/sanitize.css'
import './index.css'
export const client = new ApolloClient({
uri: 'https://fakerql.com/graphql',
});
// const gql_URL = 'http://localhost:4000';
//
// const httpLink = new HttpLink({
// uri: gql_URL,
// });
// const cache = new InMemoryCache();
// const client = new ApolloClient({
// link: httpLink,
// cache,
// });
render(
<ApolloProvider client={client}>
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
</ApolloProvider>,
document.getElementById('root')
)
```
I was importing something from index.js inside app.js , which after removal, solved the issue.
I had a similar error. I solved it by changing describe to it.

Resources