How to unit test a function that depends on Firebase reference? - javascript

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.

Related

Spying On/Mocking Import of an Import

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.

Can't mock default export in tested file

I'm working on a Typescript project and I'm trying to add tests for functions that are sorting through MongoDB responses to get meaningful results. I've set up an in-memory mock of the database, now I just need to somehow get the test to use this mock database when running tests on the functions in my file.
I have a file called mongoClient.ts that is responsible for this MongoClient object that I need to mock:
import { MongoClient } from 'mongodb';
const client = new MongoClient(`${process.env.MONGO_CONNECT}`);
export default client;
The file that I need to test, called models.ts, imports mongoClient with an ES6 import statement:
import client from '../mongoClient';
In my test I have created a MongoMemoryServer (mongodb-memory-server), and I have made a class to handle the connection to this MongoMemoryServer:
class dbConnection {
server;
connection;
uri;
async init() {
this.server = await MongoMemoryServer.create();
this.uri = await this.server.getUri();
this.connection = await new MongoClient(this.uri);
return this.connection;
}
connect() {
this.connection.connect();
}
getClient() {
return this.connection;
}
getUri() {
return this.uri;
}
async stopIt() {
await this.server.stop();
}
}
I cannot seem to get the models object that I create in the test (with const models = require('../src/models');) to mock its import of 'mongoClient'.
I've tried the following:
let mock_db = new dbConnection();
jest.doMock('../src/mongoClient', () => ({
__es6Module: true,
default: mock_db.getClient()
}));
I tried to use mock(...) earlier; that throws this error when I run the test:
The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
In a beforeAll(...) I am able to fill this DB with dummy information so my functions in models will run correctly, but these functions can't see the mock in-memory database because I can't properly mock the mongoClient file that gets imported at the top of my models.ts. I don't want to have to change any code that isn't in a .test file to accomplish this, but I can't get this test environment to point at my mock in-memory database while running these tests. I must be missing something about the implementation of doMock, and I need some help to fix it.
- -EDIT_1- - I think this might have something to do with the order in which I'm declaring things. The class dbConnection is declared directly under my imports at the top of the test file. Then under that I instantiate the dbConnection class as mockDb and jest.doMock(... like in the example shown above. My tested function seems to be trying to use a mocked import that only contains the following:
{"__es6Module":true}
The error message I'm getting at this point is ERROR --- _get__(...).db is not a function.
The problem with this could be that it's mocking the imported module correctly, but the default export used to mock (mockDb.getClient()) is undefined when it's declared in that doMock statement near the top of the file. I don't know where else to move that doMock, and it breaks if I move it into the top-level beforeAll or into the describe section for these tests or into the test itself.
- -EDIT_2- - Changing __es6Module to __esModule (because the compiled js file looks for __esModule) makes it so that undefined is the contents of the imported class in my code.
The compiled js code has the following at the top:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const mongoClient_1 = __importDefault(require("../util/mongoClient"));
- -FINAL_EDIT- - I've figured this out, it was an error on my part in how the order of the Jest tests were being executed. The default export client that I was mocking had not been initialized before the tests were being run. This required that I mock the default import in a beforeAll(...) statement inside of each 'describe' block of tests, and in each test I have to instantiate a new models at the start of the test with const models = require(...);. I have working tests now, and everything is good in life ;-)
I've figured this out, it was an error on my part in how the order of the Jest tests were being executed. The default export client that I was mocking had not been initialized before the tests were being run. This required that I mock the default import in a beforeAll(...) statement inside of each 'describe' block of tests, and in each test I have to instantiate a new models at the start of the test with const models = require(...);. I have working tests now

Installing Plaiceholder in Next.js / Webpack 5 causes: Module not found: Can't resolve 'child_process'

I'm building on Next.js app and when I install / import Plaiceholder (for generating placeholder images), I get the following error: Module not found: Can't resolve 'child_process'
Node v14.18.0
Next.js v11.1.2
Plaiceholder v2.2.0
Sharp v0.29.2
I understand this error message to mean that webpack5 is trying to bundle node packages that aren't available to the client. But I'm not clear why it is doing this. I haven't customized any of the webpack configs, and I can't find any mention of this issue in the Plaiceholder docs. How do I fix it?
NOTE: I want the base64 data URL to get created during the build, so that it available as soon as the page loads (not fetched asynchronously at run time).
Here's my next.config.js
module.exports = {
reactStrictMode: true,
};
My package.json only has scripts, dependencies, and devDependencies (nothing to change module resolution)
In case it's relevant, here's a simplified example using Plaiceholder:
import Image from "next/image";
import { getPlaiceholder } from "plaiceholder";
import React, { useState } from "react";
...
const { base64 } = await getPlaiceholder(imgUrl);
...
return (<Image
src={imgUrl}
placeholder="blur"
blurDataURL={base64}
/>);
It seems like plaiceholder is not suitable for client-side rendering. I believe that package is for the Node.js environment. That's why you get this error when you try to render your component on the client side.
To solve this problem, you need to move import { getPlaiceholder } from 'plaiceholder' to the NextJS API section. Then you can call that API with your URL data in the body. Then get the base64.
/api/getBase64.js
import { getPlaiceholder } from "plaiceholder";
export default async (req, res) => {
const { body } = req;
const { url } = body;
const { base64 } = getPlaiceholder(url);
res.status(200).send(base64);
};
/component.js
import Image from "next/image";
import React, { useState, useEffect } from "react";
const [base64, setBase64] = useState()
useEffect(() => {
(async () => {
const _base64 = await fetch.post('/api/getBase64', {url: imgUrl}); // wrote for demonstration
setBase64(_base64);
})()
})
return (<Image
src={imgUrl}
placeholder="blur"
blurDataURL={base64}
/>);
I know blurDataURL will be undefined until you fetch the data but this is the way how you can use plaiceholder library to manage your images. It should be imported only for the NodeJS environment. If you do not like this approach, you can try to find another library that also works for the browser environment (client)
UPDATED according to the comment:
If you want to generate this base64 at build time, you can use getStaticProps in the pages that use this Image component. NextJS is smart enough to understand which libraries are used in the client-side or server-side. So you can do this:
import { getPlaiceholder } from "plaiceholder"; // place it at the root of file. This will not be bundled inside of client-side code
export async function getStaticProps(context) {
const { base64 } = await getPlaiceholder(imgUrl);
return {
props: { base64 }, // will be passed to the page component as props
}
}
This way, by using getStaticProps, the page will be created at build time. You can get the base64 prop inside of the page that uses the image component and pass that prop to blurDataURL. Also, you can use this approach with getServerSideProps too.
This is from NextJS website:
Note: You can import modules in top-level scope for use in
getServerSideProps. Imports used in getServerSideProps will not be
bundled for the client-side.
https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering
It's necessary to Install plugin for Next Js dependency and configure next config based on Plaiceholder Docs for using getPlaiceholder() function in getStaticProps like the answer by #oakar.
npm i #plaiceholder/next
const { withPlaiceholder } = require("#plaiceholder/next");
module.exports = withPlaiceholder({
// your Next.js config
});

ReactJS - Importing store breaks Jest

I had a file with one function that creates an axiosInstance, it used to look like this:
import Axios from 'axios';
import getToken from '#/api/auth-token';
const [apiUrl] = [window.variables.apiUrl];
export const createApiInstance = () => {
return Axios.create({
baseURL: `${apiUrl}`,
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
};
Something we wanted to implement was notifying the user when their JWT token expires (so they won't unwittingly continue using the app while all of their requests are returning 401s). My solution turned this file into this:
import Axios from 'axios';
import getToken from '#/api/auth-token';
import store from '#/main.jsx';
import { userActionCreators } from '#/store/action-creators';
const [apiUrl] = [window.variables.apiUrl];
export const createApiInstance = (urlExtension = '') => {
const axiosInstance = Axios.create({
baseURL: `${apiUrl + urlExtension}`,
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
axiosInstance.interceptors.response.use(response => {
if (response.status === 401)
store.dispatch(userActionCreators.setUserTokenExpired());
return response;
});
return axiosInstance;
};
This worked very well, as now all of the responses from the API are being checked for a 401 Unauthorized status and dispatching an action so the app can respond to it.
Jest doesn't like the import store, so 95% of the tests in the app fail when the store is imported here. I'm certain it is the importing of the store because every test passes when it is commented it.
I've had absolutely no luck getting anything to work.
I've tried updating setting jest and babel-jest to the same versions, setting react, react-dom, and react-test-renderer to the same versions. I've looked into configuring moduleNameMapper to mock the store in the jest config in package.json but I'm not sure how to make that work. I'm starting to consider taking a completely different approach to this problem such as applying middleware to check for 401 responses instead, but I'm worried I'll end up running into the same issue after a bunch of work.
The problem with mocking the store in the test files themselves is that there's hundreds of test files in this large application, so literally anything besides adding mocking to each individual file is the solution I'm looking for.
If anyone else is having this problem, this occurred because I was exporting the store from the same file as my ReactDOM.render. Apparently you can export from this file but as soon as you try to import what is exported somewhere else it will catch it and break tests. The solution is to create and export the store from a different file.
Make sure you have a .babelrc file, as jest doesn't understand the context of babel and JSX files otherwise. Related Stack Qusetion
If that doesn't quite do the trick can you update with the main.jsx code and let me know then i'll update

Jest mocking default exports - require vs import

I have seen questions referring to the mocking of default exports with jest around here, but I don't think this has already been asked:
When mocking the default export of a dependency of a module that is being tested, the tests suite fails to run if the module imports the dependency with the ES6 import statement, stating TypeError: (0 , _dependency.default) is not a function It succeeds, however, if the module uses a require().default call instead.
In my understanding, import module from location directly translates to const module = require(location).default, so I am very confused why this is happening. I'd rather keep my code style consistent and not use the require call in the original module.
Is there a way to do it?
Test file with mock:
import './modules.js';
import dependency from './dependency';
jest.mock('./dependency', () => {
return {
default: jest.fn()
};
});
// This is what I would eventually like to call
it('calls the mocked function', () => {
expect(dependency).toHaveBeenCalled();
});
Dependency.js
export default () => console.log('do something');
module.js (not working)
import dependency from './dependency.js';
dependency();
module.js (working)
const dependency = require('./dependency.js').default;
dependency();
You can use either es6 import or require js to import your js files in your jest tests.
When using es6 import you should know that jest is trying to resolve all the dependencies and also calls the constructor for the class that you are importing. During this step, you cannot mock it. The dependency has to be successfully resolved, and then you can proceed with mocks.
I should also add that as can be seen here jest by default hoists any jest.mocks to the top of the file so the order in which you place your imports does not really matter.
Your problem though is different. Your mock function assumes that you have included your js file using require js.
jest.mock('./dependecy', () => {
return {
default: jest.fn()
};
});
When you import a file using require js, this is the structure it has:
So assuming I have imported my class called "Test" using require js, and it has method called "doSomething" I could call it in my test by doing something like:
const test = require('../Test');
test.default.doSomething();
When importing it using es6 import, you should do it differently though. Using the same example:
import Test from '../Test';
Test.doSomething();
EDIT: If you want to use es6 import change your mock function to:
jest.mock('./dependecy', () => jest.fn());
the short answer for ES module if you want to use
import dependency from 'dependency'
jest.mock('dependency', () => ({
...jest.requireActual('dependency'),
__esModule: true,
default: jest.fn(),
}))
Have you tried something like this? I was dealing with the default export mocking for months until I found this.
jest.mock('./dependency', () => () => jest.fn());
The idea behind this line is that you are exporting a module that is a function. So you need to let Jest knows that it has to mock all your ./dependency file as a function, that returns a jest.fn()

Categories

Resources