5

I hope you can help me out. I am using Nx with latest angular/nestjs (date: 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 cannot get my unit test running using NestJS with Jest I want to test following service:

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

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

and I get following error:

TypeError: Cannot read property 'get' of undefined

so it seems that the ConfigService (and also httpService) is always undefined.

when logging httpService and ConfigService, it will always be undefined. Even when I try to instantiate new Instances like new CoreApiService(new HttpService(), new ConfigService()) I've even tried things like new CoreApiService({} as any, {get: (...params} => {return 'foo'}) in the test itself

it will always be the same error mentioned above.

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 tried: .overrideProvider(ConfigService).useClass(ConfigServiceMock)

Thank you in advance!

Edit 03/01

it seems that the compile step for the module itself fails... so for the current test file the log "COMPILED" will never be executed.

 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)

current test file looks like:

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've also played with the order of the provider section, but I guess this is not relevant...

danderwald
  • 51
  • 1
  • 3
  • Remove the `ConfigModule`, but generally your mock and test setup looks fine. I'd mock the `HttpService` too, but it should work – Jay McDoniel Feb 26 '21 at 23:10
  • you can use `useValue` instead of `useClass` for `ConfigServiceMock`, then use a Map. That way you don't need to mock the `get` function. (e.g.: `const ConfigServiceMock = new Map([['foo', 'bar']]);`) – Ronen Aug 29 '21 at 06:18

2 Answers2

1

Two things that you may want to try out:

  1. Init your Nest application. Inside the beforeEach, after calling compile() in your module, add these lines:
app = module.createNestApplication();
await app.init();
  1. Don't import the ConfigModule on your test. This will initialise the actual config service, which you don't need as you're mocking it.
lsouza
  • 2,448
  • 4
  • 26
  • 39
  • Thank you for your suggestions. I've already tried to remove the ConfigModule and end up with this test, but without success – danderwald Mar 01 '21 at 07:36
  • even with the create nest application and the await after the compile, the test breaks with this error: ``` TypeError: Cannot read property 'get' of undefined so the config service is still undefined – danderwald Mar 01 '21 at 07:40
0

Hmm.. I have not tried playing with @nestjs/config but I did try out HttpService of @nestjs/axios. Like you, I wanted my test to only try calling the http methods under HttpService for the test but not actually make a http request.

The main differences in our approach are:

  1. I used useValue instead of useClass
  2. Hence, I used objects to hold my jest.fn() mocks

Put together, I did something like this in my test.ts file:

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);
    });
});

Looi
  • 96
  • 9