Search code examples
node.jsunit-testingtestingnestjssequelize-typescript

How to use value which returned from controller? Testing controllers on NestJs


Controller and method for testing:

import { Controller, Get, Response, HttpStatus, Param, Body, Post, Request, Patch, Delete, Res } from '@nestjs/common';
@Controller('api/parts')
export class PartController {
  constructor(private readonly partsService: partsService) { }

  @Get()
  public async getParts(@Response() res: any) {
    const parts = await this.partsService.findAll();
    return res.status(HttpStatus.OK).json(parts);
  }
}

And this is unit test which must test getParts method:

describe('PartsController', () => {
  let partsController: PartsController;
  let partsService: partsService;

  beforeEach(async () => {
    partsService = new partsService(Part);
    partsController= new PartsController(partsService);
  });

  describe('findAll', () => {
    it('should return an array of parts', async () => {
      const result = [{ name: 'TestPart' }] as Part[];

      jest.spyOn(partsService, 'findAll').mockImplementation(async () => result);

      const response = {
        json: (body?: any) => {
          expect(body).toBe(result);
        },
        status: (code: number) => response,
      };

      await partsController.getParts(response);
    });
  });
});

This test works correctly, but I think this is a bad solution. When I investigated this problem, I saw this option:

const response = {
  json: (body?: any) => {},
  status: (code: number) => response,
};
expect(await partsController.getParts(response)).toBe(result);

But when I try it my test don't work, cause await partsController.getParts(response) // undefined So what should I do to make my test look good?

In solution I use: nodeJS sequelize, nestJS, typescript


Solution

  • Alright so I guess your problems lies on the way you instantiate and use your controller & service.
    Let NestJs Testing utils do the job for you, like this:

    describe('Parts Controller', () => {
        let partsController: PartsController;
        let partsService: PartsService;
    
        beforeEach(async () => {
            // magic happens with the following line
            const module = await Test.createTestingModule({
                controllers: [
                    PartsController
                ],
                providers: [
                    PartsService
                    //... any other needed import goes here
                ]
            }).compile();
    
            partsService = module.get<PartsService>(PartsService);
            partsController = module.get<PartsController>(PartsController);
        });
    
        // The next 4 lines are optional and depends on whether you would need to perform these cleanings of the mocks or not after each tests within this describe section
        afterEach(() => {
            jest.restoreAllMocks();
            jest.resetAllMocks();
        });
    
        it('should be defined', () => {
            expect(partsController).toBeDefined();
            expect(partsService).toBeDefined();
        });
    
        describe('findAll', () => {
          it('should return an array of parts', async () => {
            const result: Part[] = [{ name: 'TestPart' }];
    
            jest.spyOn(partsService, 'findAll').mockImplementation(async (): Promise<Part[]> => Promise.resolve(result));
    
            const response = {
                json: (body?: any) => {},
                status: (code: number) => HttpStatus.OK,
            };
    
            expect(await partsController.getParts(response)).toBe(result);
          });
        }); 
    });
    

    I haven't tested the code myself so give it a try (not too sure about the response mocking for the Response type in the Parts Controller tho).
    Regarding the Parts Controller by the way you should take advantage of the Response type from express though - try to rewrite code as follows:

    import { Controller, Get, Response, HttpStatus, Param, Body, Post, Request, Patch, Delete, Res } from '@nestjs/common';
    import { Response } from 'express';
    
    @Controller('api/parts')
    export class PartController {
      constructor(private readonly partsService: partsService) { }
    
      @Get()
      public async getParts(@Response() res: Response) { // <= see Response type from express being used here
        const parts = await this.partsService.findAll();
        return res.status(HttpStatus.OK).json(parts);
      }
    }
    

    Finally have a look at this section of the nest official documentation, maybe it can give you some insights about what you're trying to achieve:

    In the second link, at the almost beginning of the page, it is stated in the https://docs.nestjs.com/controllers#request-object section the following:

    • For compatibility with typings across underlying HTTP platforms (e.g., Express and Fastify), Nest provides @Res() and @Response() decorators. @Res() is simply an alias for @Response(). Both directly expose the underlying native platform response object interface. When using them, you should also import the typings for the underlying library (e.g., @types/express) to take full advantage. Note that when you inject either @Res() or @Response() in a method handler, you put Nest into Library-specific mode for that handler, and you become responsible for managing the response. When doing so, you must issue some kind of response by making a call on the response object (e.g., res.json(...) or res.send(...)), or the HTTP server will hang.