What is the best way to simulate the behavior of a function that is being called by a service during testing

Currently, I am developing a NestJS project and the task at hand involves writing unit tests for my services.

One of the services in question is the BigQueryService, which utilizes @google-cloud/bigquery to interact with a Big Query dataset and execute queries. Additionally, there is another service, let's refer to it as MyService, dedicated to constructing the required query based on specific logic, passing it to the BigQueryService, retrieving the query result, and sending it back to the controller for eventual endpoint transmission.

My focus lies on creating unit tests for MyService, necessitating the need to mock the BigQueryService without having to resolve its dependencies. Below is an excerpt of the code:

bigquery.service.ts:

import { Injectable } from '@nestjs/common';
import { BigQuery } from '@google-cloud/bigquery';
...
@Injectable()
export class BigqueryService {
  ...
  constructor(
    ...
  ) {
    ...
  }

  async executeQuery(querySentence: string): Promise<Array<any>> {
    ...
    return response;
  }
}

MyService.service.ts:

import { Injectable } from '@nestjs/common';
import { BigqueryService } from '../bigquery/bigquery.service';
//the following is just a service that helps log the results of this service
import { MyLogger } from '../../config-service/logger.service';
...
@Injectable()
export class MyService {
  constructor(
    private readonly bigqueryService: BigqueryService,
    private readonly logger: MyLogger,
  ) { }
  ...
  async myFunc(request: RequestInterface): Promise<Array<ResponseInterface>> {
    let query = (some code to create a query i want)
    return await this.bigqueryService.executeQuery(query);
  }

In order to tackle the testing aspect, I referred to responses provided in this forum thread: Mock a method of a service called by the tested one when using Jest

jest.mock('../services/bigquery/bigquery.service', () => jest.fn()) 
const bq = require('../services/bigquery/bigquery.service')
jest.mock('../config-service/logger.service', () => jest.fn())
const ml = require('../config-service/logger.service')

const executeQuery = jest.fn()
executeQuery.mockReturnValue('desired value')
bq.mockImplementation(() => ({executeQuery}))


describe("Testing consumption moment service function", () => {

  it("should call the mock service", () => {
    const ms = new MyService(bq,ml)
    ms.myFunc(requestBody) //requestBody is a RequestInterface
    expect(bq.executeQuery).toHaveBeenCalled
    expect(bq.executeQuery).toHaveReturned
 });
});

After running this test, it was successful in mocking the bigquery service appropriately. However, when attempting to validate the returned value, I made the test asynchronous to ensure its completion only after myFunc has finished executing for comparison.

 it("should call the mock service", async () => {
   const ms = new MyService(bq,ml) 
   await ms.myFunc(requestBody)
   expect(bq.executeQuery).toHaveBeenCalled
   expect(bq.executeQuery).toHaveReturned
 });

Unfortunately, this resulted in an error: TypeError: this.bigqueryService.executeQuery is not a function, with the error pointing to the line where myFunc calls this.bigqueryService.executeQuery.

I have experimented with various mocking strategies to simulate the function call, but none were as effective as the aforementioned example. Additionally, I tried utilizing

jest.spyOn(bq, 'executeQuery') 

However, this also raised an issue indicating that executeQuery was not recognized as a function: Cannot spy the executeQuery property because it is not a function; undefined given instead

If anyone could provide some guidance or point me in the right direction, I would greatly appreciate any assistance. Thank you all in advance.

Answer №1

After some trial and error, I managed to solve the issue myself. For those facing a similar problem, you can find the solution at this link: https://jestjs.io/docs/en/jest-object

To fix the test, follow these steps:

jest.mock('../config-service/logger.service', () => jest.fn())
const ml = require('../config-service/logger.service')
const executeQuery = jest.fn()

describe("Testing service function", () => {

  it("should call the mock service", async () => {
    jest.mock('../services/bigquery/bigquery.service', () => {
      return {
        executeQuery: jest.fn(() => 'desired output'),
      };
    })
    const bq = require('../services/bigquery/bigquery.service')

    const ms = new MyService(bq,ml)
    const p = await ms.myFunc(requestBody) //requestBody is a RequestInterface
    expect(bq.executeQuery).toHaveBeenCalled
    expect(bq.executeQuery).toHaveReturned
    expect(p).toEqual(desired result)
 });
});

Answer №2

mockFunction(() => ({runQuery}))

This function is not asynchronous, consider returning a promise

mockFunction(() => (Promise.resolve({runQuery})))

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

A guide on integrating a submission button that combines values from multiple dropdown forms and redirects to a specific URL

Is there a way to make the submit button on this form act based on the combined values of two different dropdown menus? For instance, if "west" is selected from the first dropdown and "winter" is selected from the second dropdown, I want it to navigate to ...

Navigate through fabricated data not appearing in Express application

I have come across a handlebars template file within my Express app: {{#each data}} <article class="id-{{this.id}}"> <h1><a href="/journal/{{this.url}}">{{this.title}}</a></h1> <p>{{this.body}}</p> </ar ...

What is the best way to exclude HTML tags from the innerHTML attribute?

Currently, I am in the process of developing a messenger application and facing an issue where my messages are not ignoring HTML tags. This is happening because I directly paste the input text into the innerHTML of the message. The code snippet responsible ...

jQuery is experiencing compatibility issues with node-webkit

Currently, I am experimenting with node-webkit as a beginner in nodejs. The main content of my webpage consists of a simple html page which includes a script called main.js. To install jquery, I used the command npm install jquery index.html <!-- i ...

I'm perplexed as to why I'm receiving null for my context. Could it be due to a TypeError

Recently diving into Next Js and TypeScript, I encountered the following error: Unhandled Runtime Error TypeError: this.context is null Here's a snippet from my Layout.tsx file: import { FC } from 'react' import { Head } from 'next/d ...

Using JSON in Highcharts: Customizing Border and Label Colors

Currently using Highcharts in JSON format with the following syntax: var neutral_color = '#c4c4c4', medium_grey = '#929292'; lineChartJSON['chart']['plotBorderColor'] = medium_grey; lineChartJSON['chart&ap ...

PhoneGap switches up the error type with each consecutive run

Why does PhoneGap change errors after every time it is compiled? Sometimes it runs without any issues, but then the same code throws strange errors like parse error or function not found, even though no changes were made to the code. Here is the code that ...

Module 'angular2/angular2' not found

Currently, I am working on a node application with angular2 and gulp. One of the components I have created is login.ts: import {Component, View} from 'angular2/angular2'; import {FormBuilder, formDirectives } from 'angular2/forms'; @C ...

Utilize Lodash to execute a class instance method only if the instance exists, and is not null or

Imagine we have a scenario like the one below: class Individual { lastName: string; firstName: string; getCompleteName(separator: string) { return this.lastName + separator + this.firstName; } } const person = ... const completeName = _.ifNo ...

React Router integration problem with Semantic UI React

Just diving into ReactJS and encountering a problem with using "Menu.Item" (from Semantic UI React) and React Router. I won't include my imports here, but rest assured they are all set up correctly. The constructor in my "App.jsx" looks like this: ...

Updating an Observable in Angular 4 using HttpClient's get method

Seeking assistance in updating an observable retrieved in html format from an API call. If anyone has any insights on how to achieve this, your help would be greatly appreciated. The corresponding html (in another component) is: <common-content [them ...

Unable to modify the content inside an HTML object using an anchor tag

I am currently working on a project for a client who wants to integrate three websites into a single browser window, allowing them to interact without opening multiple windows. Originally, I thought using iframes would solve the issue, but unfortunately, t ...

Why is the Javascript Ecommerce add to cart feature failing to work properly?

I've been working on implementing an add to cart feature using a combination of Javascript and Django, with the main functionality relying on Javascript. Here's the snippet of code I have for cart.js: var updateBtns = document.getElementsByClass ...

Refresh the Google chart in response to a state change in Vuex

Currently, I am working on a reporting page that will display various graphs. Upon entering the page, an API request is made to retrieve all default information. The plan is to enable users to later select filters based on their inputs. For instance: init ...

Issue with Thymeleaf form not able to retrieve JSON object

Having trouble retrieving JSON objects instead of strings on my HTML page. Any suggestions? Below is the snippet of HTML code causing the issue: <form th:object="${case}" novalidate="true" data-model="case" action="/hcp/patient/details/insurance" meth ...

Creating code in AngularJS

I have the following template structure: <h1 class="text-center" ng-bind-html="row.text"></h1> When the content of my row.text is a string like this: Hi your name is {{ name }} It will display as shown below: Hi your name is {{ name }} ...

Issue with retrieving all phone numbers from a contact in Ionic

Hope you're doing well! I'm encountering an issue with Ionic Contacts. Currently, I'm able to retrieve all the contacts using my code but I need help extracting all the phone numbers associated with each contact. For example, if John has 3 ...

Determining if a replacement took place in the Javascript replace function

I've developed a function that scans through a set of strings and replaces specific patterns: var content = 'Here is a sample string 1/23'; var currentDate = new Date(); var currentMonth = currentDate.getMonth()+1; var currentDay = curre ...

I am unable to get the radio button checked in Angular 2

I have set up a form with two radio buttons for gender selection, and I want to ensure that the previously selected option is displayed as checked. Here's the code snippet I've added in my template: <div class="form-group"> <label& ...

Eradicate HTML by assigning empty values to certain attributes

Allowing the user to copy the HTML code of a div by clicking a button. Some attributes, such as for videos: <video loop muted autoplay> may appear like this after copying: <video loop="" muted="" autoplay=""> The ...