React Jest: How to mock Service? - reactjs

Problem
I am looking for a way to mock my keycloak service called UserService. Actually I just need to mock the isLoggedIn and the login functions. I do not think this is a difficult task, but after some hours of trying I am still not able to get a working solution for this problem.
Code Samples
UserService.js
import Keycloak from "keycloak-js";
const _kc = new Keycloak({
url: "http://localhost:80",
realm: "realmName",
clientId: "clientName"
});
const initKeycloak = (onAuthenticatedCallback, onNotAuthenticatedCallback) => {
_kc.init(
{
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
pkceMethod: 'S256',
checkLoginIframe: false
}
)
.then((authenticated) => {
if (!authenticated) {
console.error("user is not authenticated..!");
}
onAuthenticatedCallback();
})
.catch((error) => {
onNotAuthenticatedCallback();
})
};
const doLogin = _kc.login;
const doLogout = _kc.logout;
const getToken = () => {
return _kc.token;
}
const isLoggedIn = () => {
if (_kc.token) {
return true;
} else {
return false;
}
}
const updateToken = (successCallback) => {
_kc.updateToken(5)
.then(successCallback)
.catch(doLogin);
}
const getUsername = () => _kc.tokenParsed?.preferred_username;
const hasRole = (roles) => {
return roles.some((role) => _kc.hasRealmRole(role));
}
const UserService = {
initKeycloak,
doLogin,
doLogout,
isLoggedIn,
getToken,
updateToken,
getUsername,
hasRole,
};
export default UserService;
I already tried to use the jest.mock() function. So now I can push the loginButton without an error, but the addButton still does not activate.
import { render, screen } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import { BrowserRouter } from "react-router-dom";
import OrganizationsPage from "./OrganizationsPage";
import { act } from "react-test-renderer";
jest.mock("../Services/UserService", () => ({
...jest.requireActual("../Services/UserService"),
doLogin: jest.fn(() => { return true }),
isLoggedIn: jest.fn(() => { return true }),
}));
const MockedOrganizationsPage = () => {
return (
<BrowserRouter>
<OrganizationsPage />
</BrowserRouter>
);
}
describe('OrganizationsPage Alter', () => {
it('should activate alter buttons on login', async () => {
// console.log(UserService.isLoggedIn())
await act(async () => render(<MockedOrganizationsPage />))
const loginButton = screen.getByText("Login");
userEvent.click(loginButton);
const addButton = screen.getByText("Add Organization");
userEvent.click(addButton);
})
})

So I found the solution:
I do not have to mock the UserService.js file but more the keycloak-js-module. For this I create a folder called __mocks__ and put in my mocked module. Jest will automatically know to call keycloak-js from the mocks folder instead of node-modules.
src/mocks/keycloak-js.js:
class Keycloak {
token = "token available";
url = null;
realm = null;
clientId = null;
tokenParsed = {
preferred_username: "dummy user",
}
constructor(keycloakConfig) {
this.url = keycloakConfig.url;
this.realm = keycloakConfig.realm;
this.clientId = keycloakConfig.clientId;
}
login = () => {
this.token = "Logged in";
}
logout = () => {
this.token = null;
}
updateToken = () => {
this.token = "Logged in";
return new Promise((resolve, reject) => {
if (!!this.token) {
resolve();
} else {
reject();
}
})
}
init = (startupConfig) => {
}
hasRealmRole = (role) => {
return true;
}
}
export default Keycloak;

Related

React Google Recaptcha `executeRecaptcha` function always return `null`

I am using react-google-recaptcha-v3 with version ^1.10.0, but when I want to get the token from the executeRecaptcha function, the function always returns null instead of returning the token. Does anybody have a clue?
Attached code:
import React, { useState } from 'react';
import {
GoogleReCaptchaProvider,
useGoogleReCaptcha,
} from 'react-google-recaptcha-v3';
...
const AuthSigninPage = () => {
const service = new AuthService();
const isRecaptchaAvailable = true;
const setPhone = useAuthStore((state) => state.setPhone);
const { executeRecaptcha } = useGoogleReCaptcha();
const { getPhoneState } = usePhoneState();
const { push } = useRouter();
const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(true);
const authenticate = async () => {
try {
const isEligibleToLogin = await checkRecaptcha();
if (!isEligibleToLogin) return;
setPhone(phone);
const { isHasPassword, isRegistered } = await getPhoneState(phone);
if (!isRegistered) {
return;
}
push(isHasPassword ? '/auth/password' : '/auth/verify');
} catch (error: any) {
...
}
};
const checkRecaptcha = async () => {
try {
let isRecaptchaValid: boolean = true;
if (!executeRecaptcha) {
console.log('Execute recaptcha not yet available');
return;
}
if (isRecaptchaAvailable) {
const token = await executeRecaptcha!('login');
console.log(token); // always return null
if (!token) {
bottomSheet.warning({
message: 'Recaptcha token is not available',
});
return false;
}
isRecaptchaValid = await service.validateRecaptcha(token);
}
if (!isRecaptchaValid) {
bottomSheet.error({
message: 'Recaptcha is not valid',
});
}
return isRecaptchaValid;
} catch (error: any) {
JSON.stringify(error);
}
};
return (
<MainLayout
backable
title="Masuk"
>
Pretend that there is another element here like button to login
</MainLayout>
)
};
const SigninPageWrappedWithCaptcha = () => {
return (
<GoogleReCaptchaProvider
reCaptchaKey={process.env.NEXT_PUBLIC_GR_KEY as string}
>
<AuthSigninPage />
</GoogleReCaptchaProvider>
);
};
export default SigninPageWrappedWithCaptcha;

Uncaught TypeError: _Web3Client__WEBPACK_IMPORTED_MODULE_1__.mintToken.then is not a function

I am trying to integrate my smart contract with frontend react js but I am getting this error while using methods of the contract.
I got an error of react scripts version before so I installed a version less than 5 and now I am getting this error. Is it related? Please help .
Uncaught TypeError: _Web3Client__WEBPACK_IMPORTED_MODULE_1__.mintToken.then is not a function
App.js file
import React, { useEffect, useState } from 'react';
import { init, mintToken } from './Web3Client';
function App() {
const [minted, setMinted] = useState(false);
const mint = () => {
mintToken
.then((tx) => {
console.log(tx);
setMinted(true);
})
.catch((err) => {
console.log(err);
});
};
return (
<div className="App">
{!minted ? (
<button onClick={() => mint()}>Mint Token</button>
) : (
<p>Token Minted successfully</p>
)}
</div>
);
}
export default App;
Web3Client.js File
import Web3 from 'web3';
import NFTContract from './NFT.json';
let selectedAccount;
let nftContract;
let isInitialized = false;
export const init = async () => {
let provider = window.ethereum; //
if (typeof provider !== 'undefined') {
//metamask is installed
provider
.request({ method: 'eth_requestAccounts' })
.then((accounts) => {
selectedAccount = accounts[0];
console.log(`selected account is ${selectedAccount}`);
})
.catch((err) => {
console.log(err);
return;
});
window.ethereum.on('accountsChanged', function (accounts) {
selectedAccount = accounts[0];
console.log(`Selected account changed to ${selectedAccount}`);
});
}
const web3 = Web3(provider);
const networkId = await web3.eth.net.getId();
nftContract = new web3.eth.Contract(
NFTContract.abi,
NFTContract.networks[networkId].address
);
isInitialized = true;
};
export const mintToken = async () => {
if (!isInitialized) {
await init();
}
return nftContract.methods
.mint(selectedAccount)
.send({ from: selectedAccount });
};
NFT Contract
pragma solidity >=0.8.9;
import '#openzeppelin/contracts/token/ERC721/ERC721.sol';
contract NFT is ERC721 {
constructor() ERC721('Coolest NFT', 'NFT') {}
uint256 private _tokenId = 0;
function mint() external returns (uint256) {
_tokenId++;
_mint(msg.sender, _tokenId);
return _tokenId;
}
}
.then() is a callback for promises, but your mintToken() method does not return a promise. You will need to do this instead:
export const mintToken = async () => {
return new Promise(async(resolve,reject) => {
if (!isInitialized) {
await init();
}
const _txn = nftContract.methods
.mint(selectedAccount)
.send({ from: selectedAccount });
resolve(_txn);
});
};

How to wait for all useEffect Chain using Jest and Enzyme when all of them make asynchronous calls?

I have the following Minimal Component
import React, { useState, useEffect } from "react";
import { API } from "aws-amplify";
export default function TestComponent(props) {
const [appointmentId, setAppointmentId] = useState(props.appointmentId);
const [doctorId, setDoctorId] = useState(null);
useEffect(() => {
const loadDoctor = async () => {
if (doctorId) {
const doctorData = await API.post("backend", "/doctor/get", {
body: {
doctorId: doctorId
}
});
console.log("This does not come", doctorData);
}
}
loadDoctor();
}, [doctorId])
useEffect(() => {
const loadAppointment = async () => {
if (appointmentId) {
const appointmentData = await API.post("backend", "/appointment/get", {
body: {
appointmentId: appointmentId
}
});
console.log("This Loads", appointmentData);
setDoctorId(appointmentData.doctorId);
}
}
loadAppointment();
}, [appointmentId])
return (
<div>Testing Page</div>
)
}
The Following this does not work not load wait for the doctorId useEffect promise.
But the second test this does work waits for both the useEffect
import React from "react";
import { API } from "aws-amplify";
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import TestComponent from "./TestComponent.js";
jest.mock("aws-amplify");
beforeEach(() => {
API.post.mockImplementation((api, path, data) => {
if (path === "/appointment/get") {
return Promise.resolve({
doctorId: "10000001"
});
}
if (path === "/doctor/get") {
return Promise.resolve({
doctorName: "Mr. Doctor"
});
}
});
afterEach(() => {
API.post.mockClear();
});
it("this does not work", async () => {
const wrapper = mount(
<TestComponent appointmentId={"2000001"}/>
);
await act(async () => {
await Promise.resolve(wrapper);
wrapper.update();
});
// this does not print the line console.log("This does not come", doctorData);
});
it("this does work", async () => {
const wrapper = mount(
<TestComponent appointmentId={"2000001"}/>
);
await act(async () => {
await Promise.resolve(wrapper);
wrapper.update();
await act(async () => {
await Promise.resolve(wrapper);
wrapper.update();
});
});
// this prints it. This works, but this is not scalable for more complicated code
});
Is there a way I can wait for all the subsequent useEffect and then test ?
I did something similar to preload images. this put all the promisse in a stack and wait for all to resolve
preloadImages(srcs) {
function loadImage(src) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
img.src = src;
});
}
var promises = [];
for (var i = 0; i < srcs.length; i++) {
//const imgName = srcs[i].substring(15,18)
//promises.push(loadImage(srcs[i]));
promises.push(loadImage(images(`./${srcs[i]}.png`).default));
}
return Promise.all(promises);
}
then.. prelaodImages(...).then(...

How to link axios interceptors with redux store in Next.js app?

I create an API service like this:
export default class ApiService {
static init(store) {
axios.interceptors.request.use(config => {
const { token } = store.getState().user;
config.baseURL = ${process.env.NEXT_STATIC_API_URL};
if (token) {
config.headers.Authorization = Bearer ${token};
}
return config;
});
}
}
And init it here in the main app component:
class EnhancedApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
const { store } = ctx;
if (!isClient()) ApiService.init(store);
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
componentDidMount() {
ApiService.init(this.props.store);
}
render() {
const { Component, pageProps, store } = this.props;
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
}
export default withRedux(configureStore, { debug: true })(
withReduxSaga(EnhancedApp)
);
I'm using a wrapper to get user data for every page:
const checkAuth = async (ctx, isProtected) => {
const { auth } = nextCookie(ctx);
if (isProtected && !auth) {
if (!isClient() && ctx.res) {
ctx.res.writeHead(302, { Location: '/' });
ctx.res.end();
} else {
await Router.push('/');
}
}
return auth || null;
};
export const withAuth = (isProtected = false) => (WrappedComponent) => {
const WrappedWithAuth = (props) => {
const { token } = useSelector(
state => state.user
);
useEffect(() => {
if (isProtected && !token) Router.push('/');
}, [token]);
return <WrappedComponent {...props} />;
};
WrappedWithAuth.getInitialProps = async (ctx) => {
const token = await checkAuth(ctx, isProtected);
const { store } = ctx;
if (token) {
store.dispatch(userSetToken(token));
try {
const { data } = await UserService.getProfile();
store.dispatch(userGetProfileSuccess(data));
} catch (e) {
store.dispatch(userGetProfileFailure(e));
}
}
const componentProps =
WrappedComponent.getInitialProps &&
(await WrappedComponent.getInitialProps({ ...ctx, token }));
return { ...componentProps };
};
return WrappedWithAuth;
};
Everything works properly on the client-side, but when I change the user and refresh the page I see that get profile API call on the server-side continues to use a previous token.

React Authentication (Auth0) - what is the right way?

i am a newbie to react but i'm learning and need your help here.
I use Auth0 for Authentication and i have implemented their react sample in parts:
https://auth0.com/docs/quickstart/spa/react/01-login
This are parts of my code:
App.js:
<Auth0Provider
domain={AUTH_CONFIG.domain}
client_id={AUTH_CONFIG.clientId}
redirect_uri={AUTH_CONFIG.callbackUrl}
onRedirectCallback={onRedirectCallback}
>
<Router history={history}>
<RequireAuthentication>
<MyTheme>
<MyLayout />
</MyTheme>
</RequireAuthentication>
</Router>
</Auth0Provider>
Auth0Provider:
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
import jwtDecode from "jwt-decode";
import axios from "axios";
import AUTH_CONFIG from "./auth0Config";
import { useDispatch } from "react-redux";
import * as authActions from "app/auth/store/actions";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
const initAuth0 = async () => {
console.log("initAuth0 start");
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
const isAuthenticated = await auth0FromHook.isAuthenticated();
console.log("Authenticated from init: " + isAuthenticated);
setIsAuthenticated(isAuthenticated);
setLoading(false);
console.log("initAuth0 end");
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await getUserData();
setUser(user);
dispatch(authActions.setUserDataAuth0(user));
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
if (!auth0Client) {
console.warn("Auth0 Service didn't initialize, check your configuration");
return;
}
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await getUserData();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
dispatch(authActions.setUserDataAuth0(user));
};
const getAccessToken = async () => {
const accessToken = await auth0Client.getTokenSilently({
audience: AUTH_CONFIG.identity_audience,
scope: "read:allUsers read:UserPermission"
});
return accessToken;
};
const getIdToken = async () => {
if (!auth0Client) {
console.warn("Auth0 Service didn't initialize, check your configuration");
return;
}
const claims = await auth0Client.getIdTokenClaims();
return claims.__raw;
};
const getTokenData = async () => {
const token = await getIdToken();
const decoded = jwtDecode(token);
if (!decoded) {
return null;
}
return decoded;
};
const getUserData = async () => {
console.log("getuserdata");
const tokenData = await getTokenData();
const accessToken = await getAccessToken();
return new Promise((resolve, reject) => {
const { sub: userId } = tokenData;
const UserService =
"https://localhost:44312/api/v1/usermanagement/user/" + userId;
axios
.get(UserService, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": "GET,HEAD,OPTIONS,POST,PUT",
"Access-Control-Allow-Headers":
"Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers",
"Content-Type": "application/json",
Authorization: "Bearer " + accessToken
}
})
.then(response => {
resolve(response.data);
})
.catch(error => {
// handle error
console.warn("Cannot retrieve user data", error);
reject(error);
});
});
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
RequireAuthentication:
import React, { useEffect } from "react";
import { useAuth0 } from "app/auth/AuthProvider";
import { SplashScreen } from "#my";
import history from "#history";
export const RequireAuthentication = ({ children }) => {
const { isAuthenticated, loading } = useAuth0();
useEffect(() => {
console.log("checkAuth");
if (!loading) checkAuth();
// eslint-disable-next-line
}, []);
const checkAuth = () => {
console.log("checkAuth isAuthenticated: " + isAuthenticated);
console.log("checkAuth loading: " + loading);
if (!isAuthenticated && !loading) {
history.push("/login");
}
};
return isAuthenticated ? (
<React.Fragment>{children}</React.Fragment>
) : (
<SplashScreen />
);
};
callback.js:
import React, { useEffect } from "react";
import { SplashScreen } from "#my";
import { useAuth0 } from "app/auth/AuthProvider";
function Callback(props) {
const { isAuthenticated, handleRedirectCallback, loading } = useAuth0();
useEffect(() => {
const fn = async () => {
if (!loading) {
console.log("handleRedirectCallback: " + loading);
await handleRedirectCallback();
}
};
fn();
}, [isAuthenticated, loading, handleRedirectCallback]);
return <SplashScreen />;
}
export default Callback;
The problem is that the RequireAuthentication Component is rendered before the Auth0Provider is completely initialized and therefore i get never the isAuthenticated on "true".
The RequireAuthentication Component is a child of the Auth0Provider. Is it possible to wait for the Auth0Provider is fully initialized before rendering the RequireAuthentication Component???
What is the right way here?? Am I completely wrong?
Thanks
Chris
Depend on loading and isAuthenticated items in useEffect so that component will re render once they change.
useEffect(() => {
console.log("checkAuth");
if (!loading) checkAuth();
// eslint-disable-next-line
}, [loading, isAuthenticated]);

Resources