Mocking Context and Providers on JEST - javascript

I have a project where I need all the time to test components with auth.
This is a short example on how my auth context is set:
Navigation/contexts/AuthContext.js
const AuthContext = createContext();
const AuthProvider = ({children}) => {
const [auth, setAuth] = useState(false);
return (
<AuthContext.Provider value={{auth, setAuth}}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
export default AuthProvider;
I have a screen that I want to test. And this is:
Navigation/Screens/Components/LoginLoadingScreen.js
import {useAuth} from '../../contexts/AuthContext';
const LoginLoadingScreen = ({navigation}) => {
const {setAuth} = useAuth();
});
Ok. Now Im trying to test my LoginLoadingScreen with jest and testing-library
Navigation/Screens/Components/tests/LoginLoadingScreen.test.js
import React from 'react';
import LoginLoadingScreen from '../../Components/LoginLoadingScreen';
import {render, screen, fireEvent} from '#testing-library/react-native';
import {useAuth} from '../../../contexts/AuthContext';
test('LoginLoadingScreen', () => {
render(<LoginLoadingScreen />);
});
And getting this error:
TypeError: Cannot read properties of undefined (reading 'setAuth')
15 |
16 | const LoginLoadingScreen = ({navigation}) => {
> 17 | const {setAuth} = useAuth();
I will have a lot of tests that needs to use mocked auth. So. What is the better way to mock it?

You should wrap the component you are rendering with the provider preferrably using the wrapper option, something like this:
test('LoginLoadingScreen', () => {
render(<LoginLoadingScreen />, {
wrapper: ({children}) => (
<AuthProvider>
{children}
</AuthProvider>
)
});
});
This will get rid of the error on your console and make the context available to the component.
But this will increase the amount of boilerplate you tests have, in that case I would consider following the setup guide of testing library where it overrides the default render function to use all your application providers
https://testing-library.com/docs/react-testing-library/setup/

Related

How do I correctly use ReactJS Reach Router with TypeScript & ESLint?

I'm trying to implement client-only routes using Reach Router in a project using TypeScript & ESLint. This is my initial implementation:
// src/pages/app/[...].tsx
import { Router, Redirect, useLocation } from '#reach/router';
import * as React from 'react';
import BrowseMain from '../../components/BrowseMain';
import Layout from '../../components/Layout';
import UploadMain from '../../components/UploadMain';
import * as styles from '../../styles/[...].module.scss';
const IndexPage = () => {
const currLocation = useLocation();
return (
<Layout>
<main className={styles.indexMain}>
<Router basepath="/app">
{['/', '/browse', '/browse/'].map((path) => <BrowseMain key={path} path={path} />)}
{['/upload', '/upload/'].map((path) => <UploadMain key={path} path={path} />)}
{/* Redirect 404 */}
<Redirect noThrow default from={currLocation.pathname} to="/" />
</Router>
</main>
</Layout>
);
};
export default IndexPage;
// src/components/BrowseMain.tsx
import * as React from 'react';
import '../styles/BrowseMain.module.scss';
import Main from './Main';
const BrowseMain = () => <Main>Browse</Main>;
export default BrowseMain;
// UploadMain is similar to BrowseMain, so they are facing the same issue here.
// src/components/Main.tsx
import * as React from 'react';
import '../styles/Main.module.scss';
type MainProps = {
children: string,
};
const Main = ({ children }: MainProps) => <>{children}</>;
export default Main;
At this point, TypeScript threw the following error on the BrowseMain and UploadMain routes in [...].tsx:
TS2322: Type '{ key: string; path: string; }' is not assignable to type 'IntrinsicAttributes'.   Property 'path' does not exist on type 'IntrinsicAttributes'.
Following Reach Router's documentation for Usage with TypeScript, I made the following change:
import { RouteComponentProps } from '#reach/router';
import * as React from 'react';
import '../styles/BrowseMain.module.scss';
import Main from './Main';
const BrowseMain = (props: RouteComponentProps) => <Main>Browse</Main>;
export default BrowseMain;
This solves the previous linting error, however two new ones are raised:
TS6133: 'props' is declared but its value is never read.
ESLint: 'props' is defined but never used.(#typescript-eslint/no-unused-vars)
At this point, I'm stuck. I tried two different things, but they both raised linting errors:
// ESLint: Unexpected empty object pattern.(no-empty-pattern)
const BrowseMain = ({}: RouteComponentProps) => (
// ESLint: '_' is defined but never used.(#typescript-eslint/no-unused-vars)
const BrowseMain = (_: RouteComponentProps) => (
I feel like I'm in a damned if you do, damned if you don't situation right now. What's the correct way to declare an explicit type for props that will not be used?
You can specify the props using generics. In your case it will look like:
const BrowserMain: React.FC<RouteComponentProps> = () => <Main>Browse</Main
Using React.FC is however discouraged so you may also do it with a typescript call signature
type BrowserMainFC = {
(props: RouteComponentProps): JSX.Element
}
const BrowseMain: BrowserMainFC = () => <div />
Check out this resource: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components/

Get logged user every time the component renders under React ContextAPI

I'm using ContextAPI in a small React project, I use HttpOnly Cookie to store the user's token when I hit the /login endpoint.
This is UserContext.js shown bellow, which encapsulates all the components (children) in App.js
import axios from "axios";
import { createContext, useEffect, useState } from "react";
const UserContext = createContext();
const UserContextProvider = ({ children }) => {
const [loggedUser, setLoggedUser] = useState(undefined);
const checkLoggedIn = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/logged-in`);
setLoggedUser(response.data);
};
useEffect(() => {
checkLoggedIn();
}, []);
return (
<UserContext.Provider value={{ loggedUser }}>
{children}
</UserContext.Provider>
);
};
export { UserContext };
export default UserContextProvider;
What I understand is when I log in, I setLoggedUser to the state from the /login response, and now it is available for all the children components of the context.
Now I can navigate to all components wrapped by the context and print for example the email of the loggedUser, but what if the email changed while we're logged in? I'll still see the old email on my components because the data is outdated in the state. And what if token got invalidated on the server while we were logged in.. (The only case we'll get updated data is if I refresh the app because that will trigger useEffect in the context provider and refresh the state again)
Should I also pass the checkLoggedIn function through the context's value property to make it available for other components and then use it in UseEffect in every component? Or is there a better solution for this problem?
After the latest comment if you want to check for email on every re-render then you can remove [] from useEffect as stated above in the comments by #abu dujana.
import axios from "axios";
import { createContext, useEffect, useState } from "react";
const UserContext = createContext();
const UserContextProvider = ({ children }) => {
const [loggedUser, setLoggedUser] = useState(undefined);
const checkLoggedIn = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/logged-in`);
setLoggedUser(response.data);
};
useEffect(() => {
checkLoggedIn();
});
return (
<UserContext.Provider value={{ loggedUser }}>
{children}
</UserContext.Provider>
);
};
export { UserContext };
export default UserContextProvider;

Wrapping the app component with a context and getting the TypeError: (destructured parameter) is undefined

I'm trying to wrap all components under the app in a context to provide the things that I want
(as You can see it's my UserContext component)
enter code here
import React, { useState, createContext, useContext } from 'react'
const Context = createContext();
export let useUserContext = () => useContext(Context);
export default function UsersContext({ children }) {
const [users, setUsers] = useState();
const createUser = (user) => {
if (!user.name || !user.email || !user.password) return;
const newUsers = [...users, user];
setUsers(newUsers);
}
return (
<Context.Provider value={{ users, createUser }}>
{children}
</Context.Provider>
)
}
(it is my app component)
enter code here
import Nav from "./components/nav/Nav";
import Container from "./components/container/Container";
import { BrowserRouter } from "react-router-dom";
import UsersContext from "./components/contexts/UserContext";
function App() {
return (
<UsersContext>
<BrowserRouter>
<Nav />
<Container />
</BrowserRouter>
</UsersContext>
);
}
export default App;
It's used to be like this in my projects and I didn't have any problem but now
the error I'm getting "TypeError: (destructured parameter) is undefined" also says that it's because of the children in UserContext In my opinion it shouldn't happen maybe you can help me to find the problem I can't see.
Try: <Context.Provider value={[ users, { createUser } ]}>
instead of: <Context.Provider value={{ users, createUser }}>
edit:
also might try:
instead of
const newUsers = [...users, user];
setUsers(newUsers);
do
setUsers((currentUsers) => [...currentUsers, user]);
I found the problem. it was because of the useState. it was undefined and I was calling the property that related to useState at the UserContext.

React: Class Based Context API to Hook Based context API

I am really new in Hooks and during learning faces many difficulties to switch from the old style.
My old code looks like:
context.js
import React from "react";
const SomeContext = React.createContext(null);
export const withContext = (Component) => (props) => (
<SomeContext.Consumer>
{(server) => <Component {...props} server={server} />}
</SomeContext.Consumer>
);
export default SomeContext;
main index.js
<SomeContext.Provider value={new SomeClass()}>
<App />
</SomeContext.Provider>
but when I want to access it through with export default withContext(SomeComponent) by this.props.server.someFunc() it showed props is undefined in the classless hook function.
how can I achieve this.props in react hook
Edit:
SomeClass is not React inheriting class and its look like it.
class SomeClass {
someFunc = (id) => axios('api endpoints')
}
SomeComponent
const SomeComponent = () => {
...
useEffect(() => {
this.props.server.someFunc(id).then(...).catch(...)
}, ...)
...
}
In the regular case, you need to export the Context, then import it and use it within useContext:
export const SomeContext = React.createContext(null);
// Usage
import { SomeContext } from '..';
const SomeComponent = () => {
const server = useContext(SomeContext);
useEffect(() => {
server.someFunc(id);
});
};
But in your case, since you using HOC, you have the server within the prop it self:
const SomeComponent = (props) => {
useEffect(() => {
props.server.someFunc(id);
});
};

React Context testing - mocking Consumer in HOC

I am trying to test my component which is consuming data from context via HOC.
Here is setup:
Mocked context module /context/__mocks__
const context = { navOpen: false, toggleNav: jest.fn() }
export const AppContext = ({
Consumer(props) {
return props.children(context)
}
})
Higher OrderComponent /context/withAppContext
import React from 'react'
import { AppContext } from './AppContext.js'
/**
* HOC with Context Consumer
* #param {Component} Component
*/
const withAppContext = (Component) => (props) => (
<AppContext.Consumer>
{state => <Component {...props} {...state}/>}
</AppContext.Consumer>
)
export default withAppContext
Component NavToggle
import React from 'react'
import withAppContext from '../../../context/withAppContext'
import css from './navToggle/navToggle.scss'
const NavToggle = ({ toggleNav, navOpen }) => (
<div className={[css.navBtn, navOpen ? css.active : null].join(' ')} onClick={toggleNav}>
<span />
<span />
<span />
</div>
)
export default withAppContext(NavToggle)
And finally Test suite /navToggle/navToggle.test
import React from 'react'
import { mount } from 'enzyme'
beforeEach(() => {
jest.resetModules()
})
jest.mock('../../../../context/AppContext')
describe('<NavToggle/>', () => {
it('Matches snapshot with default context', () => {
const NavToggle = require('../NavToggle')
const component = mount( <NavToggle/> )
expect(component).toMatchSnapshot()
})
})
Test is just to get going, but I am facing this error:
Warning: Failed prop type: Component must be a valid element type!
in WrapperComponent
Which I believe is problem with HOC, should I mock that somehow instead of the AppContext, because technically AppContext is not called directly by NavToggle component but is called in wrapping component.
Thanks in advance for any input.
So I solved it.
There were few issues with my attempt above.
require does not understand default export unless you specify it
mounting blank component returned error
mocking AppContext with __mock__ file caused problem when I wanted to modify context for test
I have solved it following way.
I created helper function mocking AppContext with custom context as parameter
export const defaultContext = { navOpen: false, toggleNav: jest.fn(), closeNav: jest.fn(), path: '/' }
const setMockAppContext = (context = defaultContext) => {
return jest.doMock('../context/AppContext', () => ({
AppContext: {
Consumer: (props) => props.children(context)
}
}))
}
export default setMockAppContext
And then test file ended looking like this
import React from 'react'
import { shallow } from 'enzyme'
import NavToggle from '../NavToggle'
import setMockAppContext, { defaultContext } from '../../../../testUtils/setMockAppContext'
beforeEach(() => {
jest.resetModules()
})
describe('<NavToggle/>', () => {
//...
it('Should have active class if context.navOpen is true', () => {
setMockAppContext({...defaultContext, navOpen: true})
const NavToggle = require('../NavToggle').default //here needed to specify default export
const component = shallow(<NavToggle/>)
expect(component.dive().dive().hasClass('active')).toBe(true) //while shallow, I needed to dive deeper in component because of wrapping HOC
})
//...
})
Another approach would be to export the component twice, once as decorated with HOC and once as clean component and create test on it, just testing behavior with different props. And then test just HOC as unit that it actually passes correct props to any wrapped component.
I wanted to avoid this solution because I didn't want to modify project file(even if it's just one word) just to accommodate the tests.

Categories