A step-by-step guide on simulating firestore interactions using mocha

Within my codebase, I have a straightforward wrapper function that retrieves a specific document from firestore in the following manner:

export const fetchSettings = async (firestore):Promise<MyDocment>=>{
    try {
        const response = await firestore
            .collection('someCollection')
            .doc('someDoc')
            .get();
        const data = response.data()!;
        return {
          dataOne: data.someDataOne,
          dataTwo: data.someDataTwo
        };
    } catch (error) {
        console.log(error);
        return error;
    }
};

This particular function is utilized within another function that requires testing, prompting me to explore the possibility of mocking firestore to simulate returning a specific document as per the original implementation.

In jest, achieving this would involve using something along the lines of

jest.fn(()=>Promise.resolve({dataOne:'somedata', dataTwo:'someotherdata'}));

How can I replicate this behavior using mocha?

Answer №1

When working with Mocha, it's important to note that it doesn't have built-in stub/mock functions. In order to implement these functionalities, you can make use of an external library such as sinonjs.

For example:

fetchSettings.ts:

interface MyDocment {}

export const fetchSettings = async (firestore): Promise<MyDocment> => {
  try {
    const response = await firestore.collection('someCollection').doc('someDoc').get();
    const data = response.data()!;
    return {
      dataOne: data.someDataOne,
      dataTwo: data.someDataTwo,
    };
  } catch (error) {
    console.log(error);
    return error;
  }
};

fetchSettings.test.ts:

import { fetchSettings } from './fetchSettings';
import sinon from 'sinon';

describe('66868604', () => {
  it('should return data', async () => {
    const mData = {
      someDataOne: 'someDataOne',
      someDataTwo: 'someDataTwo',
    };
    const mRes = { data: sinon.stub().returns(mData) };
    const mFirestore = {
      collection: sinon.stub().returnsThis(),
      doc: sinon.stub().returnsThis(),
      get: sinon.stub().resolves(mRes),
    };
    const actual = await fetchSettings(mFirestore);
    sinon.assert.match(actual, { dataOne: 'someDataOne', dataTwo: 'someDataTwo' });
    sinon.assert.calledWithExactly(mFirestore.collection, 'someCollection');
    sinon.assert.calledWithExactly(mFirestore.doc, 'someDoc');
    sinon.assert.calledOnce(mFirestore.get);
    sinon.assert.calledOnce(mRes.data);
  });

  it('should handle error', async () => {
    const mErr = new Error('stackoverflow');
    const mFirestore = {
      collection: sinon.stub().returnsThis(),
      doc: sinon.stub().returnsThis(),
      get: sinon.stub().rejects(mErr),
    };
    const actual = await fetchSettings(mFirestore);
    sinon.assert.match(actual, mErr);
    sinon.assert.calledWithExactly(mFirestore.collection, 'someCollection');
    sinon.assert.calledWithExactly(mFirestore.doc, 'someDoc');
    sinon.assert.calledOnce(mFirestore.get);
  });
});

Unit test results:

  66868604
    ✓ should return data
Error: stackoverflow
    at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/66868604/fetchSettings.test.ts:25:18
    at Generator.next (<anonymous>)
    at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/66868604/fetchSettings.test.ts:8:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/66868604/fetchSettings.test.ts:4:12)
    at Context.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/66868604/fetchSettings.test.ts:24:40)
    at callFn (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:364:21)
    at Test.Runnable.run (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:352:5)
    at Runner.runTest (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:677:10)
    at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:801:12
    at next (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:594:14)
    at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:604:7
    at next (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:486:14)
    at Immediate.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:572:5)
    at processImmediate (internal/timers.js:461:21)
    ✓ should handle error


  2 passing (10ms)

------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
All files         |     100 |      100 |     100 |     100 |                   
 fetchSettings.ts |     100 |      100 |     100 |     100 |                   
------------------|---------|----------|---------|---------|-------------------

Answer №2

If you're working with Mocha, keep in mind that it doesn't come with a built-in mock library. You'll need to select one and manually integrate it into your tests.

To get started with mocking, install sinon by running:

npm install sinon

Then make sure to include it in your code like this:

const sinon = require("sinon");

Next, leverage a stub for defining your function's behavior:

Stubs are essentially functions (or spies) with predefined actions.

// create a stub for an anonymous function
const stub = sinon.stub();
// specify the resolved value (in case of promises)
stub.resolves({dataOne: 'some data', dataTwo: 'some other data'});
// use "returns" for regular functions instead
// stub.returns(obj);

Now, you can invoke stub() as if it were your actual function.

For similar outcomes, consider exploring fakes or mocks.

Refer to the setup guide offered by Sinon to steer clear of common pitfalls.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Variables in Angular DI become undefined when a method is called through a passed function reference

Utilizing Angular, I have a class with several Dependency Injection (DI) variables. During normal execution of a method within the class, everything functions as expected and I can access the injected instances of the DI variables. Now, my goal is to crea ...

Utilizing RXJS in Angular to pull information from numerous services within a single component

There are two services, ser1 and ser2. getdata1() { this.http.get<{message:string,Data1:any}>('http://localhost:3000/api/1') .pipe(map((data1)=>{ return Data1.Data1.map(data=>{ return { id: d ...

Utilize VueJS to upload and visualize a file input on your website

I am currently working with TypeScript and Haml in conjunction with vue.js. My goal is to enable users to upload and view a file seamlessly using the vue.js framework. I have successfully managed to upload an image, however, I am facing an issue where the ...

What is the best way to dynamically add fields to every object in an array of Firestore documents using RxJS?

Trying to solve a challenging RxJS issue here. In my Angular service class, I initially had a method that fetched data from the Firebase Firestore database like this: async getAllEmployees() { return <Observable<User[]>> this.firestore.co ...

Exploring the ideal workspace for maximizing the potential of Ionic framework development

For my iOS and Android app built with Ionic and Angular, I typically use ionic serve in Chrome for easy and fast development. However, when it comes to utilizing the native HTTP API for essential tasks like communicating with a REST server, I've foun ...

Encountering a problem while attempting to transfer data from a functional component to a class component in React with Typescript

In one of my pages, specifically the roleCategory.tsx (a functional component), I am utilizing the Navigate function to send a value named FromPage to another page called Home.tsx (which is a Class component). Below is the snippet of code where this functi ...

Exploring methods to retrieve the status attribute of an Angular httpClient response

Here is the code snippet that I am working with: this.http.post(url, payload) .subscribe( (req:any)=>{ if(req.status != 200){ console.log('non 200 status'); The this.http in my code refers to a service tha ...

Experimenting with the onClose callback that is activated upon clicking outside of the component

Testing the functionality of a popup component with a click away listener that closes when clicked outside. it.only('clicking outside popover calls on close callback ', async () => { const onCloseCallback = jest.fn(); const { queryByTe ...

Ensuring that Vue3 Typescript app focuses on input element within Bootstrap modal upon opening

I am facing a challenge with setting the focus on a specific text input element within a modal dialog. I have tried various methods but none seem to achieve the desired outcome. Here is what I have attempted: Attempt 1: Using autofocus attribute. <inpu ...

The model does not align with the body request, yet it is somehow still operational

My programming concept: class User { username: string; password: string; } Implementation of my function: const userList = []; function addUser(newUser: User) { userList.push(newUser); } Integration with Express router: router.post('/user ...

Guide to dynamically setting the index element with ngFor in Angular

When working with NgFor in Angular, I am interested in dynamically handling attributes using an index. I have a collection of properties/interfaces that look like this: vehicle1_Name, vehicle2_Name, vehicle3_Name vehicle4_Name, totalVehCount To achieve t ...

Guide on extracting the id from the path parameter in an HTTP request using Cloud Functions and nodejs

I am currently in the process of developing a serverless application using GCP Cloud Functions (nodejs). I have successfully implemented different behaviors based on the request method, but I am facing an issue with retrieving the id from the path paramete ...

Using Partial function input in TypeScript

I am in need of a function that can accept partial input. The function includes a variable called style, which should only have values of outline or fill, like so: export type TrafficSignalStyle = 'outline' | 'fill' let style: TrafficSi ...

The JavaScript code is executing before the SPFX Web Part has finished loading on the SharePoint page

I recently set up a Sharepoint Page with a custom masterpage, where I deployed my SPFx Webpart that requires certain javascript files. While the Webpart functions correctly at times, there are instances when it doesn't work due to the javascript bein ...

Is it possible to determine the type of an array value similar to how we can determine the type

Here is something I can accomplish: const keys = { "hi": {name: "ho"} } type U = [keyof typeof keys][0]; // "hi" But can I also achieve the same with array values? const data = [ { name: "hi" } ]; type T = typeof data[0]["name"]; // string not " ...

Preserving type information in TypeScript function return values

Wondering how to make this code work in TypeScript. Function tempA copies the value of x.a to x.temp and returns it, while function tempB does the same with x.b. However, when calling tempA, the compiler seems to forget about the existence of the b field ...

Can someone point me to the typescript build option in Visual Studio 2019 Community edition?

When I created a blank node.js web app on VS 2015, there was an option under project properties called "TYPESCRIPT BUILD" that allowed me to configure settings like combining JavaScript output into a single file. After upgrading to VS 2019 Community, I ca ...

RangeError: The React application has surpassed the maximum stack size limit, causing an error to be thrown

Hey there, I could use a hand. I'm fairly new to React and attempting to develop an application for managing contacts by adding them to Local Storage and deleting them. Below is the code snippet from my App.js file: import React, {useState, useEffect} ...

Building an interactive menu in Angular: A step-by-step guide

I am working with an Interface that looks like this: export interface INavData { name?: string; url?: string | any[]; icon?: string; } To populate this Interface, I use the following data structure: export const navItems: INavData[] = [ { ...

The specified 'Promise<Modules>' type argument cannot be assigned to the parameter of type '(params: any) => Promise<Modules>' in the current context

Looking for some help with this helper function that I need to call, it has the following signature: export const fetchPaginated = async <T>( callback: (params: any) => Promise<T>, { page = 1, pageSize, }: { page?: number; page ...