NestJS Test: Configuring Dependency Injection with CreateTestingModule

I'm in need of some assistance. I am currently working with Nx along with the latest versions of Angular/NestJS (February 26)

    ...
    "@nestjs/common": "^7.0.0",
    "@nestjs/config": "^0.6.3",
    "@nestjs/core": "^7.0.0",
    "@nestjs/platform-express": "^7.0.0",
    "@nestjs/platform-socket.io": "^7.6.7",
    "@nestjs/websockets": "^7.6.7",
    "jest": "26.2.2",
    "@nrwl/jest": "11.4.0",
    ...

I'm encountering issues running unit tests using NestJS and Jest I specifically want to test the following service:

@Injectable()
export class CoreApiService {
  logger = new Logger('CoreApiService');
  apiEndpoint;

  constructor(private httpService: HttpService, configService: ConfigService) {
    this.apiEndpoint = configService.get('API_SERVICE_ENDPOINT');
  }
}

However, I keep getting this error:

TypeError: Cannot read property 'get' of undefined

It appears that both ConfigService and HttpService are always undefined.

Even when attempting to create new instances like

new CoreApiService(new HttpService(), new ConfigService())
I've also tried variations such as
new CoreApiService({} as any, {get: (...params} => {return 'foo'})
within the test itself

The error persists despite these efforts.

Here's the test file:

import { Test, TestingModule } from '@nestjs/testing';
import { CoreApiService } from './core-api.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HttpModule } from '@nestjs/common';


class ConfigServiceMock {
  get(key: string): string {
    switch (key) {
      case 'API_SERVICE_ENDPOINT':
        return '';
    }
  }
}

describe('CoreApiService', () => {
  let module: TestingModule;
  let service: CoreApiService;

  beforeEach(async () => {
    module = await Test.createTestingModule({
      imports: [HttpModule, ConfigModule],
      providers: [
        CoreApiService,
        { provide: ConfigService, useClass: ConfigServiceMock },
      ],
    }).compile();
    service = module.get<CoreApiService>(CoreApiService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });
});


I've even attempted:

.overrideProvider(ConfigService).useClass(ConfigServiceMock)

Thank you for your help!

Edit 03/01

It seems that the module compilation step is failing... hence, the log "COMPILED" will not be executed in the current test file.

 CoreApiService › should be defined

    TypeError: Cannot read property 'get' of undefined

      22 |
      23 |   constructor(private httpService: HttpService, configService: ConfigService) {
    > 24 |     this.apiEndpoint = configService.get('API_SERVICE_ENDPOINT');
         |                                      ^
      25 |   }
      26 |
      27 |   private static createRequestConfig(options: RequestHeader): RequestConfig {

      at new CoreApiService (src/app/core-api/core-api.service.ts:24:38)
      at Injector.instantiateClass (../../node_modules/@nestjs/core/injector/injector.js:286:19)
      at callback (../../node_modules/@nestjs/core/injector/injector.js:42:41)
      at Injector.resolveConstructorParams (../../node_modules/@nestjs/core/injector/injector.js:114:24)
      at Injector.loadInstance (../../node_modules/@nestjs/core/injector/injector.js:46:9)
      at Injector.loadProvider (../../node_modules/@nestjs/core/injector/injector.js:68:9)
          at async Promise.all (index 5)
      at InstanceLoader.createInstancesOfProviders (../../node_modules/@nestjs/core/injector/instance-loader.js:43:9)
      at ../../node_modules/@nestjs/core/injector/instance-loader.js:28:13
          at async Promise.all (index 1)
      at InstanceLoader.createInstances (../../node_modules/@nestjs/core/injector/instance-loader.js:27:9)
      at InstanceLoader.createInstancesOfDependencies (../../node_modules/@nestjs/core/injector/instance-loader.js:17:9)
      at TestingModuleBuilder.compile (../../node_modules/@nestjs/testing/testing-module.builder.js:43:9)

  ● CoreApiService › should be defined

    expect(received).toBeDefined()

    Received: undefined

      44 |   it('should be defined', () => {
      45 |     console.log('SHOULD BE DEFINED')
    > 46 |     expect(service).toBeDefined();
         |                     ^
      47 |   });
      48 | });
      49 |

      at Object.<anonymous> (src/app/core-api/core-api.service.spec.ts:46:21)

The current test file looks like this:

import { Test, TestingModule } from '@nestjs/testing';
import { CoreApiService } from './core-api.service';
import { ConfigService } from '@nestjs/config';
import { HttpService, INestApplication } from '@nestjs/common';

class ConfigServiceMock {
  get(key: string): string {
    switch (key) {
      case 'API_SERVICE_ENDPOINT':
        return '';
    }
  }
}

class HttpServiceMock {
  get(): any {

  }
}

describe('CoreApiService', () => {
  let module: TestingModule;
  let service: CoreApiService;
  let app: INestApplication;

  beforeEach(async () => {
    console.log('beforeEach')
    module = await Test.createTestingModule({
      imports: [],
      providers: [
        { provide: ConfigService, useClass: ConfigServiceMock },
        { provide: HttpService, useClass: HttpServiceMock },
        CoreApiService,
      ],
    })
      .compile();
    console.log('COMPILED');
    app = module.createNestApplication();
    await app.init();
    service = module.get<CoreApiService>(CoreApiService);
  });

  it('should be defined', () => {
    console.log('SHOULD BE DEFINED')
    expect(service).toBeDefined();
  });
});

I have also experimented with changing the order of the provider section, but it doesn't seem to make a difference...

Answer №1

Here are a couple of suggestions for you to consider:

  1. In your Nest application initialization process, make sure to create and initialize the app inside the beforeEach function after calling compile() within your module setup:
app = module.createNestApplication();
await app.init();
  1. Avoid importing the ConfigModule in your test environment to prevent unnecessary initialization of the config service, which can be mocked instead.

Answer №2

Interesting, I haven't had the chance to experiment with @nestjs/config yet, but I have delved into using the HttpService from @nestjs/axios. In my case, I specifically aimed to test the functionality of the http methods within the HttpService without actually triggering a real http request.

Our approaches differ in a few key aspects:

  1. I opted for utilizing useValue over useClass
  2. Therefore, I relied on objects to store my mocks created with jest.fn()

To sum it up, here is a snippet from my test.ts file demonstrating my approach:

import { Test, TestingModule } from '@nestjs/testing';
import { RegistrationService } from './registration.service';
import { HttpService } from '@nestjs/axios';

let registrationService: RegistrationService;
let httpService: HttpService;
const mockHttpService = {
  axiosRef: {
    request: jest.fn(),
    post: jest.fn(),
  },
};

describe('RegistrationService', () => {
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        RegistrationService,
        HttpService,  
        {
          provide: HttpService,
          useValue: mockHttpService,
        },

      ],
    }).compile();
    registrationService = module.get<RegistrationService>(RegistrationService);
    httpService = module.get<HttpService>(HttpService);

  describe('registerUser', () => {
    const mockRegistrationPayload = {
      identityNumber: '12345',
    };

    it('should return registration details on successful request', async () => {
      // Prepare
      const mockRegistrationResponse = {
        RespData: {
          ResponseCode: '200',
          ResponseDescription: 'Pending processing',
      };
      mockHttpService.axiosRef.request.mockResolvedValueOnce({
        data: mockRegistrationResponse,
      });
                                                    
      // Action
      const res = await registrationService.registerUser(mockRegistrationPayload);

      // Assert
      expect(httpService.axiosRef.request).toBeCalled();
      expect(res).toEqual(mockRegistrationResponse);
    });
});

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

Bringing in the typescript 4 package to use in typescript version 3.8.3

I've been working on a project that is utilizing typescript 3.8.3, and I'm currently attempting to import a newer package (specifically win32-api). I initially didn't anticipate any issues since the package is compiled to JavaScript. At wor ...

Implementing an interface in TypeScript and assigning it a value using generics?

I'm struggling to make sense of a type declaration I came across in the react-router library: export interface RouteComponentProps< Params extends { [K in keyof Params]?: string } = {}, C extends StaticContext = StaticContext, S = H.Lo ...

Troubleshooting: Angular 13 input radio not recognizing checked condition

Storing selectedKitchenId in localstorage, and checking if selectedKitchenId === kitchen.id to determine which radio button should be selected. Cannot figure out why the checked condition is not working as expected, even though when I use a strong tag to d ...

Is $onInit utilizing a separate scope for its execution?

HTML: <map street="{{firstunit.street}}"/> Component: @Component('CustomerService', { templateUrl: '/CustomerService/_UnitCard/MapComponent/Map.html', selector: 'map', bindings: { street: '@&a ...

Sorting in an Angular mat table can be customized to consider two properties within a single

Trying to implement a table with MatSort, I've encountered an issue where every column sorts except for the third one: <ng-container matColumnDef="nbreEnregistrementTotal"> <th mat-header-cell *matHeaderCellDef mat-sort-header ...

Migration unsuccessful due to incompatible peer dependencies detected - Updating Angular to Version 12 was not successful

Currently in the process of upgrading my Angular v11 apps to Angular v12. Encountering an error while attempting to upgrade Angular packages: ng update @angular/core@12 @angular/cli@12 Error: Migration failed due to incompatible peer dependencies The pa ...

Generating a TypeScript type based on import.meta.glob's information

Currently, I am importing multiple JSON files that contain an id within their data. export type CourseInfo = { id: string; name: string; fullName: string; sku: string | null; par: string; buyCourseDescription: string | null; price: string; ...

TS2345 error: Typescript compilation failed due to the argument type 'FlattenMaps' being incorrect

Encountering an issue with the compilation of typescript (npm run build) due to a nested schema problem. The step (hubs.push(h.toJSON());) is resulting in a TS2345 error code. Currently attempting to upgrade nodejs and mongodb versions but unsure of what m ...

Perform a child component function in Angular

I'm working on a project with a child component as a map and a parent component as a form. The parent component has a field for writing the address, and when an address is entered, an HTTP request is triggered to find the latitude and longitude coordi ...

Unlock specific elements within the "sub-category" of a combined collection

If my union type is structured like this: type StateUpdate = { key: 'surname', value: string } | { key : 'age', value: number }; This setup is convenient because it allows me to determine the type of the value based on the key. Howev ...

Typescript: Utilizing a generic array with varying arguments

Imagine a scenario where a function is called in the following manner: func([ {object: object1, key: someKeyOfObject1}, {object: object2, key: someKeyOfObject2} ]) This function works with an array. The requirement is to ensure that the key field co ...

Angular 13 - Encountering issue with "Cannot access properties of null (reading 'getParsed')"

Currently working on a new Angular 13 project and running into an error: TypeError: Unable to access properties of null (reading 'getParsed') at .../main.2506c840be361c93.js:1:325924 at Array.filter () at nd._getActiveElements (.../main.2506c84 ...

Unable to display surface chart using Plotly in Angular 12

I've been attempting to display a 3D model using Plotly (https://github.com/plotly/angular-plotly.js/blob/master/README.md), but unfortunately, the chart is not appearing. component.component.ts import { Component } from '@angular/core'; @ ...

What is the process for classifying a nested object?

How can I specify a nested object? Click here for image description An error occurred: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ UA: {}; RU: {}; }'. No index ...

The conflict between ESLint's object curly new line rule and Prettier's multi-line formatting

I'm brand new to the world of prettier, typescript, and eslint. Even though most of the integration is going smoothly, I am facing an issue with multi-line destructuring objects. Prettier Version 1.19.1 Playground link Input: const { ciq, draw ...

Introducing the 'node' type in tsconfig leads to errors in type definitions

I've encountered an issue with my NodeJS project where I have a type declaration file to add properties to the Request object: @types/express/index.d.ts: import { User } from "../../src/entity/user.entity"; declare global { namespace Exp ...

Working with <html> response data in place of Json in Angular 6

My Angular 6 application needed to call a web service that returns HTML data, which I then had to display within a div. Below is an example of the HTML response data from the service: <html> <head> <title>Chart : 180: Abraham, Male, 1 ...

Issues are arising with Angular Form where the FormControl is not being properly set up for the first field in my form

After grappling with this issue for several weeks, I am still unable to pinpoint the cause. (Angular version: 16.1.4) The form component is populated using a BehaviorSubject, and although the console prints out the correct values for both the form and dat ...

Why does TypeScript trigger an ESLint error when using `extend` on a template string?

I am looking to create a TrimStart type in the following way: type TrimStart<T extends string> = T extends ` ${infer Rest}` ? TrimStart<Rest> : T; type TT = TrimStart<' Vue React Angular'>; // 'Vue React Angular' H ...

The 'replace' property is not found in the 'string' type

I am encountering a perplexing error code TS2339: Property 'X' is not found on type 'Y'. How can I resolve this issue? I have included libraries in my 'tsconfig.jsonc' file: "compilerOptions": { "target": "es3", // "es3" ...