3
votes

I'm trying to a test a async function of a service in nestJS.

this function is async... basically get a value (JSON) from database (using repository - TypeORM), and when successfully get the data, "transform" to a different class (DTO)... the implementation:

async getAppConfig(): Promise<ConfigAppDto> {
  return this.configRepository.findOne({
    key: Equal("APPLICATION"),
  }).then(config => {
    if (config == null) {
      return new class implements ConfigAppDto {
        clientId = '';
        clientSecret = '';
      };
    }
    return JSON.parse(config.value) as ConfigAppDto;
  });
}

using a controller, I checked that this worked ok. Now, I'm trying to use Jest to do the tests, but with no success... My problem is how to mock the findOne function from repository..

Edit: I'm trying to use @golevelup/nestjs-testing to mock Repository!

I already mocked the repository, but for some reason, the resolve is never called..

describe('getAppConfig', () => {
  const repo = createMock<Repository<Config>>();

  beforeEach(async () => {
    await Test.createTestingModule({
      providers: [
        ConfigService,
        {
          provide: getRepositoryToken(Config),
          useValue: repo,
        }
      ],
    }).compile();
  });

  it('should return ConfigApp parameters', async () => {
    const mockedConfig = new Config('APPLICATION', '{"clientId": "foo","clientSecret": "bar"}');
    repo.findOne.mockResolvedValue(mockedConfig);
    expect(await repo.findOne()).toEqual(mockedConfig); // ok

    const expectedReturn = new class implements ConfigAppDto {
      clientId = 'foo';
      clientSecret = 'bar';
    };
    expect(await service.getAppConfig()).toEqual(expectedReturn);

    // jest documentation about async -> https://jestjs.io/docs/en/asynchronous
    // return expect(service.getAppConfig()).resolves.toBe(expectedReturn);
  });
})
  • the expect(await repo.findOne()).toEqual(mockedConfig); works great;
  • expect(await service.getAppConfig()).toEqual(expectedReturn); got a timeout => Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout;

using debug, I see that the service.getAppConfig() is called, the repository.findOne() too, but the .then of repository of findOne is never called.

Update: I'm trying to mock the repository using @golevelup/nestjs-testing, and for some reason, the mocked result don't works on service. If I mock the repository using only jest (like code below), the test works... so, I think my real problem it's @golevelup/nestjs-testing.

...
provide: getRepositoryToken(Config),
useValue: {
  find: jest.fn().mockResolvedValue([new Config()])
},
...
3
What does your ConfigService look like?Baboo
It's the first part of the code in the question: async getAppConfig(): Promise<ConfigAppDto>Roberto Correia
My question was on the implementation of the ConfigService ;) How do you load the env vars? Can you show us?Baboo
i don't get! no env vars used! the ConfigService.APP_CONFIG_KEY its just a static string with value "APPLICATION". Almost the entire ConfigService it's here... the only part that I don't included its the static string, imports, and constructor (with @InjectRepository, because this is not related with the problem... I will update the code)Roberto Correia
What if you comment ` expect(await repo.findOne()).toEqual(mockedConfig); // ok`? Maybe jest is mocking only in the first time, just guessing.Natan Deitch

3 Answers

3
votes

So, my real problem is how I'm mocking the Repository on NestJS. For some reason, when I mock using the @golevelup/nestjs-testing, weird things happens!

I really don't found a good documentation about this on @golevelup/nestjs-testing, so, I gave up using it.

My solution for the question was to use only Jest and NestJS functions... the result code was:

Service:

// i'm injecting Connection because I need for some transactions later;
constructor(@InjectRepository(Config) private readonly configRepo: Repository<Config>, private connection: Connection) {}

async getAppConfig(): Promise<ConfigApp> {
  return this.configRepo.findOne({
    key: Equal("APPLICATION"),
  }).then(config => {
    if (config == null) {
      return new ConfigApp();
    }
    return JSON.parse(config.value) as ConfigApp;
  })
}

Test:

describe('getAppConfig', () => {
  const configApi = new Config();
  configApi.key = 'APPLICATION';
  configApi.value = '{"clientId": "foo", "clientSecret": "bar"}';

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        ConfigAppService,
        {
          provide: getRepositoryToken(Config),
          useValue: {
            findOne: jest.fn().mockResolvedValue(new
            Config("APPLICATION", '{"clientId": "foo", "clientSecret": "bar"}')),
          },
        },
        {
          provide: getConnectionToken(),
          useValue: {},
        }
      ],
    }).compile();

    service = module.get<ConfigAppService>(ConfigAppService);
  });

  it('should return ConfigApp parameters', async () => {
    const expectedValue: ConfigApp = new ConfigApp("foo", "bar");

    return service.getAppConfig().then(value => {
      expect(value).toEqual(expectedValue);
    })
  });
})

some sources utilized for this solution: https://github.com/jmcdo29/testing-nestjs/tree/master/apps/typeorm-sample

1
votes

I think expect(await repo.findOne()).toEqual(mockedConfig); works because you mocked it, so it returns right away. In the case of expect(await service.getAppConfig()).toEqual(expectedReturn);, you did not mock it so it is probably taking more time, thus the it function returns before the Promise resolved completely.

The comments you posted from jest documentation should do the trick if you mock the call to getAppConfig().

service.getAppConfig = jest.fn(() => Promise.resolve(someFakeValue))

or

spyOn(service, 'getAppConfig').and.mockReturnValue(Promise.resolve(fakeValue))
0
votes

This answer from @roberto-correia made me wonder if there must be something wrong with the way we are using createMock from the package @golevelup/nestjs-testing.

It turns out that the reason why the method exceeds the execution time has to do with the fact that createMock does not implement the mocking, and does not return anything, unless told to do so.

To make the method work, we have to make the mocked methods resolve something at the beginning of the test:

usersRepository.findOneOrFail.mockResolvedValue({ userId: 1, email: "[email protected]" });

A basic working solution:

describe("UsersService", () => {
  let usersService: UsersService;
  const usersRepository = createMock<Repository<User>>();

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: getRepositoryToken(User),
          useValue: usersRepository,
        },
    }).compile();

    usersService = module.get(UsersService);
  });

  it("should be defined", () => {
    expect(usersService).toBeDefined();
  });
  it("finds a user", async () => {
    usersRepository.findOne.mockResolvedValue({ userId: 1, email: "[email protected]" });

    expect(await usersRepository.findOne()).toBe({ userId: 1, email: "[email protected]" });
  });
});