NestJS architecture and testing

I am currently working on a project using NestJS and when it comes to testing I think the architecture is bit of a pain. As far as I understand the recommended approach is to have two layers: controllers and service.

Controllers

The controllers is fairly easy to test when needed, although I do not understand the need for testingModules (same goes for services):

const module = await Test.createTestingModule({
  controllers: [MyController]
  providers: [MyService] // jest.mock is used to mock MyService
}).compile();

controller = module.get(MyController)

It seems fairly easy to inject whatever mock I need directly:

controller = new MyController(new MyService()) // again jest.mock is used to mock MyService

For me the latter example is shorter, easier to understand and expresses exactly what I want to test. It does not test that my dependency injection works, but (at least in our application) that is checked by NestJS at startup.

Services

When it comes to testing services, I find myself doing a lot of mocking of repositories.

const spy = jest.spyOn(repository, 'save')
  .mockReturnValueOnce(null) // something like jest-when should be introduced

await service.create('Question on Stackoverflow', 'I do not get it...')

expect(spy.mock.calls[0][0]).toEqual({
  title: 'Question on Stackoverflow',
  content: 'I do not get it...',
  comments: [] // no initial comments
})

Every tutorial/example I've found suggests putting the Repository in the service. I've thought about putting it in the controller (or resolver for GraphQL), but is just doesn't feel like the right separation of responsibilities and leads to duplication of code when both Rest and GraphQL is used. For me the best approach seems to be creating another layer (or add functions to my entity classes), which would then be responsible for the domain/business logic. This is easy to test (no mocks and no promises)

const question = Domain.create('Question on Stackoverflow', 'I do not get it...')
// or const question = Question.create('Question on Stackoverflow', 'I do not get it...')

expect(question).toEqual({
  title: 'Question on Stackoverflow',
  content: 'I do not get it...',
  comments: [] // no initial comments
})

The service would then only be responsible for I/O which seems like a great separation of concerns.

My question is now: does this make sense when it comes to NestJS? It seems like this is not the suggested way to do it. Maybe I am missing some important stuff or maybe I am complicating the architecture more that necessary.

Thanks for your inputs in advance
Andreas

Upvotes: 1

Views: 1728

Answers (1)

Jay McDoniel
Jay McDoniel

Reputation: 70490

The ability to do something like

const myController = new MyConstroller(new MyService());

is great in theory, but once your service starts having dependencies on other services (Redis, TypeORM, other services in your application) it can quickly become handful to work with

const myController = new MyController(
  new MyService(
    new RedisService(redisOptions),
    new MyEntityReposiotry(myEntityRepoOptions),
    new OtherService(new RedisService(redisOpitons))
  )
);

and sure you can set these to variables before making your controller so you're instantiating less classes, but you're still having to make a lot of these injections yourself.

With the Nest architecture and Test class you can do something quick like

beforeEach(async () => {
  const module = await Test.createTestingModule({
    controllers: [MyController],
    providers: [
      {
        provide: MyService,
        useValue: myServiceMock
      }
    ],
  }).compile();
});

And now you easily have a reference to what your mock for my service is, you can define the general structure and default return values of the service functions, and can still get the reference to the service and controller class with module.get<MySerivce|MyController>(MyService|MyController).

Of course, you can always use jest.mock() to start a basic stub and mock the return values as you see fit, but keep in mind how that can affect the look of your tests. One of the draws of NestJS is that it is opinionated and it has a certain way it likes to look.

If you'd like to see examples, you an check out this repo with a bunch of test samples for different types of projects, including rxjs, typeorm, and graphql.

Upvotes: 0

Related Questions