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.