I need to mock useLogin hook, bacause it contains logic that throws an error. Later i will test it in isolationю. UseLogin returns loading flag and login function that connecting new user to firebase.
import useLogin from "../../../hooks/useLogin";
export default function Login({ register, handleSubmit }) {
const [loading, login] = useLogin();
return (
<Form onSubmit={handleSubmit(login)}>
{...other components}
<Form>
);
}
useLogin hook
import { useState } from "react";
import { useHistory } from "react-router";
import { useModalContext } from "../../context";
import { firebase } from "../../libs/firebase";
export default function useLogin() {
const [loading, setLoading] = useState(false);
const [, { showErrorModal }] = useModalContext();
const history = useHistory();
function login({ email, password }) {
setLoading(true);
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
setLoading(false);
history.push("/");
})
.catch(() => {
setLoading(false);
showErrorModal("User not found");
});
}
return [loading, login];
}
You should avoid mocking your own components when possible, as you will need to maintain both component and mock if you change the component code. You can still test this and test the useLogin hook isolated, to test all different code branches and so on.
The recommendation when going to the outside world and make API requests, is to use a library that allows you to mock them without being intrusive. Libraries like nock or Mock Service Worker let you do exactly that, and this way you avoid mocking fetch.
That said, in case you need to mock this, you can mock the firebase library to return whatever is needed. You can do it with a regular Jest mock, or try this firebase-mock library in case you will need to mock more of firebase features and here you can see how it integrates with Jest.
Creating your own custom mock might be the quicker option, and if you don't need to mock anything else, it might be the best option as well. Here you can see an example of a custom firebase mock: Mock implementation of firebase auth methods after mocking the firebase module
Related
I'm writing unit tests using vitest on a VueJS application.
As part of our application, we have a collection of API wrapper services, e.g. users.js which wraps our relevant API calls to retrieve user information:
import client from './client'
const getUsers = () => {
return client.get(...)
}
export default {
getUsers
}
Each of these services utilise a common client.js which in turn uses axios to do the REST calls & interceptor management.
For our units tests, I want to check that the relevant url is called, so want to spy on, or mock, client.
I have followed various examples and posts, but struggling to work out how I mock an import (client) of an import (users.js).
The closest I've been able to get (based on these posts - 1, 2) is:
import { expect, vi } from 'vitest'
import * as client from '<path/to/client.js>'
import UsersAPI from '<path/to/users.js>'
describe('Users API', () => {
beforeEach(() => {
const spy = vi.spyOn(client, 'default') // mock a named export
expect(spy).toHaveBeenCalled() // client is called at the top of users.js
})
test('Users API.getUsers', () => {
UsersAPI.getUsers()
expect(spy).toHaveBeenCalled()
})
})
but it's tripping on:
❯ async frontend/src/api/client.js:3:31
2| import store from '#/store'
3|
4| const client = axios.create({
| ^
5| headers: {
6| 'Content-Type': 'application/json'
where it's still trying to load the real client.js file.
I can't seem to mock client explicitly because the import statements run first, and so client is imported inside users.js before I can modify/intercept it. My attempt at the mocking was as follows (placed between the imports and the describe):
vi.mock('client', () => {
return {
default: {
get: vi.fn()
}
}
})
Mocking a module
vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:
// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js') // => resolves to path/to/client.js
It often helps to use path aliases here.
Spying/mocking a function
To spy on or mock a function of the mocked module, do the following in test():
Dynamically import the module, which gets the mocked module.
Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '#/users.js'
vi.mock('#/client')
describe('Users API', () => {
test('Users API.getUsers', async () => {
1️⃣
const client = await import('#/client')
2️⃣
const response = { data: [{ id: 1, name: 'john doe' }] }
client.default.get = vi.fn().mockResolvedValue(response)
const users = await UsersAPI.getUsers()
expect(client.default.get).toHaveBeenCalled()
expect(users).toEqual(response)
})
})
demo
Late to the party but just in case anyone else is facing the same issue.
I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.
import { client } from 'client';
vi.mock('client', () => {
const client = vi.fn();
client.get = vi.fn();
return { client }
});
Then in those tests calling client.get() behind the scenes as a dependency, just add
client.get.mockResolvedValue({fakeResponse: []});
and the mocked function will be called instead of the real implementation.
If you are using a default export, look at the vitest docs since you need to provide a default key.
If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.
I've accepted the above answer, as that did address my initial question, but also wanted to include this additional step I required.
In my use case, I need to mock an entire module import, as I had a cascading set of imports on API files that in turn, imported more and more dependencies themselves.
To cut this, I found this in the vuex documentation about mocking actions:
https://vuex.vuejs.org/guide/testing.html#testing-actions
which details the use of webpack and inject-loader to substitute an entire module with a mock, preventing the source file loading at all.
Trying out Next.js but I'm struggling with the following. Just tried to install react-hook-mousetrap and imported it like I normally would:
import useMousetrap from "react-hook-mousetrap";
This gives me the following error:
SyntaxError: Cannot use import statement outside a module
1 > module.exports = require("react-hook-mousetrap");
I am not sure what this means? I then thought it might be a problem with Nextjs's SSR, since my library enables hotkeys and will use some browser APIs. If you already know that I am on the wrong track here you can stop reading now.
What I did next however was this, I tried dynamic imports:
1. Dynamic import with next/dynamic
First thing I came across was next/dynamic, but this seems to be for JSX / React Components only (correct me if I'm wrong). Here I will be importing and using a React hook.
2. Dynamic import with await (...).default
So I tried dynamically importing it as a module, but I'm not sure how to do this exactly.
I need to use my hook at the top level of my component, can't make that Component async and now don't know what to do?
const MyComponent = () => {
// (1) TRIED THIS:
const useMousetrap = await import("react-hook-mousetrap").default;
//can't use the hook here since my Component is not async; Can't make the Component async, since this breaks stuff
// (2) TRIED THIS:
(async () => {
const useMousetrap = (await import("react-hook-mousetrap")).default;
// in this async function i can't use the hook, because it needs to be called at the top level.
})()
//....
}
Any advice here would be appreciated!
The error occurs because react-hook-mousetrap is exported as an ESM library. You can have Next.js transpile it using next-transpile-modules in your next.config.js.
const withTM = require('next-transpile-modules')(['react-hook-mousetrap']);
module.exports = withTM({ /* Your Next.js config */});
I don't know if my answer is actual, but i'm facing whith this problem today, and what that i done:
//test component for modal
const Button: React.FC<{close?: () => void}> = ({ close }) => (
<React.Fragment>
<button type="button" onClick={ close }>Close</button>
</React.Fragment>
);
// async call import react custom hook in next js whithout a dynamic
//import
let newHook;
(async () => {
const { hookFromNodeModules } =
await import('path/to/hook');
newHook = hookFromNodeModules;
})();
export default function Home() {
// check if hook is available
const openModal = newHook && newHook();
const { t } = useTranslation('common');
// useCallback for update call function when hook is available
const handleOpen = React.useCallback(() => {
openModal?.openModal(Button, {});
}, [openModal]);
return ( ...your code )
}
hope this help!)
screen from next.js app
Trying out Next.js but I'm struggling with the following. Just tried to install react-hook-mousetrap and imported it like I normally would:
import useMousetrap from "react-hook-mousetrap";
This gives me the following error:
SyntaxError: Cannot use import statement outside a module
1 > module.exports = require("react-hook-mousetrap");
I am not sure what this means? I then thought it might be a problem with Nextjs's SSR, since my library enables hotkeys and will use some browser APIs. If you already know that I am on the wrong track here you can stop reading now.
What I did next however was this, I tried dynamic imports:
1. Dynamic import with next/dynamic
First thing I came across was next/dynamic, but this seems to be for JSX / React Components only (correct me if I'm wrong). Here I will be importing and using a React hook.
2. Dynamic import with await (...).default
So I tried dynamically importing it as a module, but I'm not sure how to do this exactly.
I need to use my hook at the top level of my component, can't make that Component async and now don't know what to do?
const MyComponent = () => {
// (1) TRIED THIS:
const useMousetrap = await import("react-hook-mousetrap").default;
//can't use the hook here since my Component is not async; Can't make the Component async, since this breaks stuff
// (2) TRIED THIS:
(async () => {
const useMousetrap = (await import("react-hook-mousetrap")).default;
// in this async function i can't use the hook, because it needs to be called at the top level.
})()
//....
}
Any advice here would be appreciated!
The error occurs because react-hook-mousetrap is exported as an ESM library. You can have Next.js transpile it using next-transpile-modules in your next.config.js.
const withTM = require('next-transpile-modules')(['react-hook-mousetrap']);
module.exports = withTM({ /* Your Next.js config */});
I don't know if my answer is actual, but i'm facing whith this problem today, and what that i done:
//test component for modal
const Button: React.FC<{close?: () => void}> = ({ close }) => (
<React.Fragment>
<button type="button" onClick={ close }>Close</button>
</React.Fragment>
);
// async call import react custom hook in next js whithout a dynamic
//import
let newHook;
(async () => {
const { hookFromNodeModules } =
await import('path/to/hook');
newHook = hookFromNodeModules;
})();
export default function Home() {
// check if hook is available
const openModal = newHook && newHook();
const { t } = useTranslation('common');
// useCallback for update call function when hook is available
const handleOpen = React.useCallback(() => {
openModal?.openModal(Button, {});
}, [openModal]);
return ( ...your code )
}
hope this help!)
screen from next.js app
Trying out Next.js but I'm struggling with the following. Just tried to install react-hook-mousetrap and imported it like I normally would:
import useMousetrap from "react-hook-mousetrap";
This gives me the following error:
SyntaxError: Cannot use import statement outside a module
1 > module.exports = require("react-hook-mousetrap");
I am not sure what this means? I then thought it might be a problem with Nextjs's SSR, since my library enables hotkeys and will use some browser APIs. If you already know that I am on the wrong track here you can stop reading now.
What I did next however was this, I tried dynamic imports:
1. Dynamic import with next/dynamic
First thing I came across was next/dynamic, but this seems to be for JSX / React Components only (correct me if I'm wrong). Here I will be importing and using a React hook.
2. Dynamic import with await (...).default
So I tried dynamically importing it as a module, but I'm not sure how to do this exactly.
I need to use my hook at the top level of my component, can't make that Component async and now don't know what to do?
const MyComponent = () => {
// (1) TRIED THIS:
const useMousetrap = await import("react-hook-mousetrap").default;
//can't use the hook here since my Component is not async; Can't make the Component async, since this breaks stuff
// (2) TRIED THIS:
(async () => {
const useMousetrap = (await import("react-hook-mousetrap")).default;
// in this async function i can't use the hook, because it needs to be called at the top level.
})()
//....
}
Any advice here would be appreciated!
The error occurs because react-hook-mousetrap is exported as an ESM library. You can have Next.js transpile it using next-transpile-modules in your next.config.js.
const withTM = require('next-transpile-modules')(['react-hook-mousetrap']);
module.exports = withTM({ /* Your Next.js config */});
I don't know if my answer is actual, but i'm facing whith this problem today, and what that i done:
//test component for modal
const Button: React.FC<{close?: () => void}> = ({ close }) => (
<React.Fragment>
<button type="button" onClick={ close }>Close</button>
</React.Fragment>
);
// async call import react custom hook in next js whithout a dynamic
//import
let newHook;
(async () => {
const { hookFromNodeModules } =
await import('path/to/hook');
newHook = hookFromNodeModules;
})();
export default function Home() {
// check if hook is available
const openModal = newHook && newHook();
const { t } = useTranslation('common');
// useCallback for update call function when hook is available
const handleOpen = React.useCallback(() => {
openModal?.openModal(Button, {});
}, [openModal]);
return ( ...your code )
}
hope this help!)
screen from next.js app
I want to unit test a function in my application that calls and updates a Firebase reference. The problem I am facing is that when I try to run the test and import the file that contains the function, I get the following error SyntaxError: Unexpected token u in JSON at position 0 which points to the line in my function file that imports my reference to Firebase.
I am using Webpack and Babel on this project, so I tried setting a resolve.alias in the webpack.config file which worked when the application was running, but did not work when I ran npm test. At this point I'm at a loss as to how I can mock out the Firebase reference so I can test the other functions of the function. Here's some sample code:
constants/index.js
import firebase from 'firebase';
const firebaseConfig = JSON.parse(unescape(`${config.FIREBASE_CONFIG}`));
const mainApp = firebase.initializeApp(firebaseConfig);
export const ref = mainApp.database().ref();
actions/settings.js
import * as actions from './';
import { ref } from '../constants';
export const updateSetting = (e, value) =>
(dispatch) => {
ref.child('setting')
.set(value)
.then(() => {
dispatch(actions.confirmFBSave());
})
.catch((err) => {
dispatch(actions.failFBSave({ text: err.code }));
});
};
What testing framework are you using Qunit, jasmine, mocha? With AngularFire and Angular it is much easier because you can pass a mock class in at DI, but I have also tested non angular js for server side. Then I used Sinon to get mock initializeApp and return a Mock firebase app.
You could also try https://github.com/thlorenz/proxyquire I didn't need to go that far, but I have seen it suggested. That way you are essentially intercepting the import and swap it out for something you can test with.