Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting Mocks in NestJS Application for Contract Testing

Issue

I'm looking for a way to bring up a NestJS application with mocked providers. This is necessary for provider contract tests because a service needs to be brought up in isolation. Using the Pact library, testing the provider assumes that the provider service is already running. It needs to be able to make HTTP requests against the actual server (with some dependencies mocked if necessary). PactJS

Current Research

I've looked into the docs for NestJS and the closest solution I can find is pasted below. From what I can tell, this solution tells the module to replace any provider called CatsService with catsService. This theoretically would work for provider contract testing purposes, but I don't think this allows for the entire app to be brought up, just a module. There is no mention in the docs for being able to bring up the app on a specific port using the testing module. I've tried to call app.listen on the returned app object and it fails to hit a breakpoint placed right after the call.

import * as request from "supertest";
import { Test } from "@nestjs/testing";
import { CatsModule } from "../../src/cats/cats.module";
import { CatsService } from "../../src/cats/cats.service";
import { INestApplication } from "@nestjs/common";

describe("Cats", () => {
  let app: INestApplication;
  let catsService = { findAll: () => ["test"] };

  beforeAll(async () => {
    const module = await Test.createTestingModule({
      imports: [CatsModule]
    })
      .overrideProvider(CatsService)
      .useValue(catsService)
      .compile();

    app = module.createNestApplication();
    await app.init();
  });

  it(`/GET cats`, () => {
    return request(app.getHttpServer())
      .get("/cats")
      .expect(200)
      .expect({
        data: catsService.findAll()
      });
  });

  afterAll(async () => {
    await app.close();
  });
});

Java Example

Using Spring a configuration class, mocks can be injected into the app when running with the "contract-test" profile.

@Profile({"contract-test"})
@Configuration
public class ContractTestConfig {

  @Bean
  @Primary
  public SomeRepository getSomeRepository() {
    return mock(SomeRepository.class);
  }

  @Bean
  @Primary
  public SomeService getSomeService() {
    return mock(SomeService.class);
  }
} 
like image 785
Mojo982 Avatar asked Nov 09 '18 17:11

Mojo982


1 Answers

Update

Since version 4.4 you can also use listen since it now also returns a Promise.


You have to use the method listenAsync instead of listen so that you can use it with await:

beforeAll(async () => {
  const moduleFixture = await Test.createTestingModule({
    imports: [AppModule],
  })
    .overrideProvider(AppService).useValue({ root: () => 'Hello Test!' })
    .compile();

  app = moduleFixture.createNestApplication();
  await app.init();
  await app.listenAsync(3000);
        ^^^^^^^^^^^^^^^^^^^^^
});

Then you can make actual http requests instead of relying on supertest. (I am using the nodejs standard http library in this example.)

import * as http from 'http';

// ...

it('/GET /', done => {
  http.get('http://localhost:3000/root', res => {
    let data = '';
    res.on('data', chunk => data = data + chunk);
    res.on('end', () => {
      expect(data).toEqual('Hello Test!');
      expect(res.statusCode).toBe(200);
      done();
    });
  });
});

Don't forget to close the application or otherwise your test will run until closed manually.

afterAll(() => app.close());
like image 158
Kim Kern Avatar answered Nov 11 '22 10:11

Kim Kern