React testing library, firebase, mocked functions - reactjs

I started studying integration testing and I am struggling with mocking.
I have a signup form that onSubmit calls firebase.auth.createUserWithEmailAndPassword, after that it initializes a document with the returned user uid and initialize it with default values.
I managed to check if createCserWithEmailAndPassword is correctly called but when I try to check if the collection set has been called I receive as error:
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has value: undefined
I am struggling here.
Here is my test implementation:
import React from "react";
import "#testing-library/jest-dom";
import { ThemeProvider } from "styled-components";
import { defaultTheme } from "../../styles";
import { createMemoryHistory } from "history";
import { Router, Switch, Route } from "react-router-dom";
import { render, fireEvent, wait } from "#testing-library/react";
import { SignUp } from "../../sections";
import { User } from "../../types";
import { auth, firestore } from "../../lib/api/firebase";
jest.mock("../../lib/api/firebase", () => {
return {
auth: {
createUserWithEmailAndPassword: jest.fn(() => {
return {
user: {
uid: "fakeuid",
},
};
}),
signOut: jest.fn(),
},
firestore: {
collection: jest.fn(() => ({
doc: jest.fn(() => ({
collection: jest.fn(() => ({
add: jest.fn(),
})),
set: jest.fn(),
})),
})),
},
};
});
const defaultUser: User = {
isActive: false,
accountName: "No balance",
startingBalance: 0.0,
monthlyBudget: 0.0,
};
const history = createMemoryHistory({ initialEntries: ["/signup"] });
const renderWithRouter = () =>
render(
<ThemeProvider theme={defaultTheme}>
<Router history={history}>
<Switch>
<Route exact path="/signup">
<SignUp />
</Route>
<Route exact path="/">
<div data-testid="GenericComponent"></div>
</Route>
</Switch>
</Router>
</ThemeProvider>
);
describe("<SignUp/>", () => {
it("correctly renders the signup form", () => {
const { getByTestId } = renderWithRouter();
const form = getByTestId("SignupForm");
expect(form).toBeInTheDocument();
});
it("correctly renders the signup form", () => {
const { getByTestId } = renderWithRouter();
const form = getByTestId("SignupForm");
expect(form).toBeInTheDocument();
});
it("let user signup with valid credentials", async () => {
const { getByPlaceholderText, getByTestId } = renderWithRouter();
const emailField = getByPlaceholderText("yourname#company.com");
const passwordField = getByPlaceholderText("Password");
const confirmPasswordField = getByPlaceholderText("Confirm Password");
const submitButton = getByTestId("Button");
fireEvent.change(emailField, {
target: { value: "test#test.com" },
});
fireEvent.change(passwordField, { target: { value: "Lampone01!" } });
fireEvent.change(confirmPasswordField, {
target: { value: "Lampone01!" },
});
fireEvent.click(submitButton);
expect(submitButton).not.toBeDisabled();
await wait(() => {
expect(auth.createUserWithEmailAndPassword).toHaveBeenCalled();
expect(
firestore.collection("users").doc("fakeUid").set(defaultUser)
).toHaveBeenCalled();
expect(history.location.pathname).toBe("/dashboard");
});
});
});
What am I doing wrong?
Thanks guys.

Related

React component test case coverage of inline functions

Can someone help me cover this test case, I am not able to figure out how to cover this inline function
Note: DropdownField is a wrapper component and contains the actual which is imported from
import { Field } from "redux-form";
dropdown input inside
I have tried to call mockfunction and jest.fn() but nothing works, Any help will be appreciated because I am totally blank at the moment. Thanks in advance to all the wonderful devs
import React from "react";
import DropdownField from "components/FormFields/DropdownField";
import get from "lodash/get";
 
const AddressLookup = props => {
const {
change,
formValues,
fetchAddressLookup,
postalCodeOptions,
type = "delivery",
placeholder = "type_to_search",
field
} = props;
const selectedDeliveryMethod = get(formValues, "delivery_method", {});
 
return (
<DropdownField
placeholder={placeholder}
options={postalCodeOptions}
{...selectedDeliveryMethod.fields.zip_code}
isSearchable={true}
field={field}
onInputChange={value => {
if (value.length >= 2) fetchAddressLookup({ q: value });
}}
onChange={({ value }) => {
const [city, state, zipCode] = value.split("-");
change(field, value);
change(`${type}_state`, state);
change(`${type}_city`, city);
change(`${type}_zip_code`, zipCode);
}}
/>
);
};
 
export default AddressLookup;
I have tried this approach but It failed to cover. First test case covers the UI part only as you can see it is matching to snapshot. In second test cases I removed some code and commented some because nothing works
import * as React from 'react';
import { render, fireEvent, wait } from '#testing-library/react';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import { reduxForm } from 'redux-form';
import configureStore from 'redux-mock-store';
import messages from '__fixtures__/messages.json';
import AddressLookup from '../index';
const DecoratedAddressLookup = reduxForm({ form: 'testForm' })(AddressLookup);
const testProps = {
change: jest.fn(),
fetchAddressLookup: jest.fn(),
postalCodeOptions: [
{
name: 'abc-abcd-1234',
value: 'abc-abcd-1234',
},
],
formValues: {
delivery_method: {
fields: {
zip_code: 'BD-BDBD-1234',
},
},
},
field: 'zip_code',
};
describe('<AddressLookup />', () => {
let testStore;
let addressField;
const mockStore = configureStore([]);
const store = mockStore({});
const spy = jest.fn();
beforeAll(() => {
testStore = store;
});
const renderComponent = () => {
return render(
<Provider store={testStore}>
<IntlProvider locale='en' messages={messages}>
<DecoratedAddressLookup
{...testProps}
onInputChange={spy}
onChange={spy}
/>
</IntlProvider>
</Provider>
);
};
it('should render and match the snapshot', () => {
const {
getByTestId,
container: { firstChild },
} = renderComponent();
addressField = getByTestId('zip_code');
expect(firstChild).toMatchSnapshot();
});
it('should type a value', async () => {
addressField = addressField.querySelector('input');
// expect(addressField).toBeTruthy();
// console.log('addressField', addressField);
// const input = screen.getByTestId('add-word-input');
fireEvent.change(addressField, { target: { value: 'abc-abcd-1234' } });
expect(addressField).toHaveValue('abc-abcd-1234');
// expect(testProps.change).toBeCalled();
await wait(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});

How to test state management(Recoil) using react jest?

How do I test recoil using react jest?
I expect below test is going to be success but it gives me fail.
Any way to render the status of isLogin: false using jest?
// src/state/user.ts
import { atom } from "recoil";
export type UserType = {
isLogin: boolean;
};
const userState = atom<UserType>({
key: "user",
default: {
isLogin: true,
},
});
export default userState;
// src/pages/user/User.tsx
import { useNavigate, useParams } from "react-router-dom";
import { useRecoilValue } from "recoil";
import userState from "../../state/user";
export default function User() {
const navigate = useNavigate();
const { id } = useParams();
const { isLogin } = useRecoilValue(userState);
if (!isLogin) {
return <div>Login 후 이용 가능합니다.</div>;
}
return (
<div>
{id}
<button
type="button"
onClick={() => {
navigate("/");
}}
>
Go to Home
</button>
</div>
);
}
// src/pages/user/User.test.tsx
import { MemoryRouter } from "react-router-dom";
import { render, screen, renderHook, act } from "#testing-library/react";
import { RecoilRoot, useSetRecoilState } from "recoil";
import User from "./User";
import type { UserType } from "../../state/user";
import userState from "../../state/user";
const userStateMock = (user: UserType) => {
const { result } = renderHook(() => useSetRecoilState(userState), {
wrapper: RecoilRoot,
});
act(() => {
result.current(user);
});
return result;
};
describe("<User />", () => {
const renderUserComponent = () =>
render(
<RecoilRoot>
<MemoryRouter>
<User />
</MemoryRouter>
</RecoilRoot>
);
describe("When user hasn't logged in", () => {
it("Should render warning message", () => {
userStateMock({
isLogin: false,
});
renderUserComponent();
expect(screen.getByText(/Login 후 이용 가능합니다./)).toBeDefined();
});
});
});
Result of the test

expect(jest.fn()).toHaveBeenCalled()

I want to test that specific method/s are called when the user clicks 'sign in with microsoft' button.
Unfortunately, I'm receiving the below error in the image:
[enter image description here][1]
What I want is to mock the one specific method/instance that cause the sign up with microsoft pop up to occur on button click.
Below is my test file:
Login.test.js:
import * as React from 'react';
import {
render, screen, waitFor
} from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import { MuiThemeProvider } from '#material-ui/core';
import { SnackbarProvider } from 'notistack';
import { I18nextProvider } from 'react-i18next';
import Login from '../../pages/Login';
import theme from '../../styles/theme';
import i18n from '../../i18n/index';
const mockLoginWithMicrosoft = jest.fn();
jest.mock('../../config/Firebase', () => ({
__esModule: true,
default: {
getCurrentUserId: jest.fn(),
loginWithMicrosoft: () => mockLoginWithMicrosoft,
}
}));
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
test('Firebase signInWithPopUp triggered on login button ', async () => {
render(
<MuiThemeProvider theme={theme}>
<SnackbarProvider>
<I18nextProvider i18n={i18n}>
<Login />
</I18nextProvider>
</SnackbarProvider>
</MuiThemeProvider>
);
expect(<Login />).toBeTruthy();
userEvent.click(screen.getByRole('button', { name: /sign in with microsoft/i }));
await waitFor(() => expect(mockLoginWithMicrosoft).toHaveBeenCalled());
});
And here is my firebase setup:
Firebase/index.js:
class Firebase {
constructor() {
app.initializeApp(config);
app.analytics();
this.perf = app.performance();
this.auth = app.auth();
this.db = app.firestore();
this.microsoftProvider = new app.auth.OAuthProvider('microsoft.com');
this.microsoftProvider.setCustomParameters({
tenant: '4b9d21d4-5ce6-4db6-bce6-cfcd1920afbc',
});
this.microsoftProvider.addScope('GroupMember.Read.All');
}
async loginWithMicrosoft() {
try {
const result = await this.auth.signInWithPopup(this.microsoftProvider);
const {
accessToken,
} = result.credential;
await this.getUserRoles(accessToken);
this.refreshRoles(true);
return {
message: 'success',
};
} catch (error) {
return {
message: 'failure',
};
}
}
some code
As you see above, I expect loginWithMicrosoft to be called, when clicking login button as shown below in login file:
Login.js:
import { useSnackbar } from 'notistack';
import { Redirect } from 'react-router-dom';
import Button from '#material-ui/core/Button';
import { useTranslation } from 'react-i18next';
import { DASHBOARD as DASHBOARD_PATH } from '../../navigation/CONSTANTS';
import firebase from '../../config/Firebase';
const Login = (props) => {
const { history } = props;
const classes = useStyles(props);
const { enqueueSnackbar } = useSnackbar();
const { t } = useTranslation();
const [loadState, setLoadState] = useState(false);
if (firebase.getCurrentUserId()) {
return (<Redirect to={DASHBOARD_PATH} />);
}
async function login() {
try {
setLoadState(true);
await firebase.loginWithMicrosoft();
history.replace(DASHBOARD_PATH);
} catch (error) {
enqueueSnackbar(t(translationKeys.snackbar.loginFailed), { variant: 'error' });
setLoadState(false);
}
}
return (
<div>
some code
<Button
data-testid="submitbtn"
disabled={loadState}
type="submit"
fullWidth
variant="contained"
color="primary"
startIcon={<img src={MicrosoftIcon} alt="" />}
className={classes.submit}
onClick={() => { login(); }}
>
{t(translationKeys.button.loginWindows)}
</Button>
</Grid>
<Footer />
</Grid>
</div>
);
};
);
};
Any help would be appreciated.
Okay so someone helped solve this problem,
As it was explained to me,
LoginWithMicrosoft doesn't need to be an arrow function since
jest.fn already returns a function
The other problem is jest hoisting the mock call above the fn declaration
My code now looks like this:
import MockFirebase from '../../config/Firebase';
jest.mock('../../config/Firebase', () => ({
__esModule: true,
default: {
getCurrentUserId: jest.fn(),
loginWithMicrosoft: jest.fn(),
}
}));
test('Firebase signInWithPopUp triggered on login button ', async () => {
render(
<MuiThemeProvider theme={theme}>
<SnackbarProvider>
<I18nextProvider i18n={i18n}>
<Login />
</I18nextProvider>
</SnackbarProvider>
</MuiThemeProvider>
);
userEvent.click(screen.getByRole('button', { name: /sign in with microsoft/i }));
expect(MockFirebase.loginWithMicrosoft).toHaveBeenCalled();
});

Jest mock variables of imported component

I am trying to mock a variable (auth) inside my App component as it is doing conditional rendering. How should I do it without trying to export the variable itself? Been trying for a few days with various solutions but I can't seem to cover it, and now I am stuck.
App.js
import React from "react";
import { useRoutes } from "react-router-dom";
import Routing from "./routes";
import useAuth from "./hooks/useAuth";
import SplashScreen from "./components/splashScreen/SplashScreen";
const App = () => {
const content = useRoutes(Routing());
const auth = useAuth();
return (
<>
{auth.isInitialized ? content : <SplashScreen />}
</>
);
};
export default App;
App.test.js
import React from "react";
import { mount } from "enzyme";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
describe("App Unit Tests", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<BrowserRouter>
<App />
</BrowserRouter>
);
});
it("App should render", () => {
expect(wrapper.length).toEqual(1);
});
//Below fails
it("should render splashscreen", () => {
jest.mock("./hooks/useAuth", () => ({
isInitialized: false,
}));
expect(wrapper.length).toEqual(1);
});
it("should render content", () => {
jest.mock("./hooks/useAuth", () => ({
isInitialized: true,
}));
expect(wrapper.length).toEqual(1);
});
});
You could do something like this:
jest.mock('./hooks/use-auth', () => ({
isInitialized: true
});
This basically means that use-auth returns an object which has a inInitialized property
Instead of auth, the useAuth hook should be mocked into an object (say mockUseAuth) that has the isInitialized getter. The getter should return a mockIsInitialized value, that can be changed on per test case basis. Something like this :
let mockIsInitialized = true;
let mockUseAuth = {
isAuthenticated: true
};
Object.defineProperty(mockUseAuth, 'isInitialized', {
get: jest.fn(() => mockIsInitialized)
});
jest.mock('./hooks/use-auth', () => {
return jest.fn(() => (mockUseAuth))
})
describe("App Unit Tests", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<BrowserRouter>
<App />
</BrowserRouter>
);
});
it("App should render", () => {
expect(wrapper.length).toEqual(1);
});
it("should render splashscreen", () => {
mockIsInitialized = false;
expect(wrapper.length).toEqual(1);
});
it("should render content", () => {
mockIsInitialized = true;
expect(wrapper.length).toEqual(1);
});
});

Testing a React with React-Router v.5, useHistory, useSelector and useEffect

I struggle with writing a proper test for a component that protects some routes and programatically redirects unauthorized users. The component looks like this:
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { isAuthed } from 'redux/selectors/auth';
const mapState = state => ({
isAuthenticated: isAuthed(state),
});
const LoginShield = ({ children }) => {
const { isAuthenticated } = useSelector(mapState);
const history = useHistory();
useEffect(() => {
if (!isAuthenticated) {
history.push('/login');
}
}, [isAuthenticated]);
return children;
};
export default LoginShield;
I basically would like to check that the component redirects unauthenticated user and doesn't redirect an authenticated user (two basic test cases). I tried several approaches using Jest/Enzyme or Jest/ReactTestingLibrary and cannot find a good solution.
For now my test is a mess but I will share it so that someone can show me where the problem lays:
import React, { useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import rootReducer from 'redux/reducers';
import LoginShield from 'components/LoginShield/LoginShield';
describe('LoginShield component', () => {
let wrapper;
let historyMock;
beforeEach(() => {
const initialState = { auth: { loginId: 'Foo' } };
const store = createStore(rootReducer, initialState);
historyMock = {
push: jest.fn(),
location: {},
listen: jest.fn(),
};
jest.mock('react-redux', () => ({
useSelector: jest.fn(fn => fn()),
}));
wrapper = mount(
<Provider store={store}>
<Router history={historyMock}>
<LoginShield>
<h5>Hello Component</h5>
</LoginShield>
</Router>
</Provider>,
);
});
it('renders its children', () => {
expect(wrapper.find('h5').text()).toEqual('Hello Component');
});
it('redirects to the login page if user is not authenticated', async () => {
await act(async () => {
await Promise.resolve(wrapper);
await new Promise(resolve => setImmediate(resolve));
wrapper.update();
});
// is the above necessary?
console.log(historyMock.push.mock.calls);
// returns empty array
// ... ?
});
it('doesn`t redirect authenticated users', () => {
// .... ?
});
});
Any tips are more than welcome! Thank you in advance. :)

Resources