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

1

5

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

Williamson answered 27/9, 2019 at 13:55 Comment(1)
Is there any reason you want to inject the response and manage it your self instead of having Nest take care of it for you?Appal
S
9

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.
Saddle answered 1/10, 2019 at 22:53 Comment(4)
I am following your solution but I keep getting Type '{ json: (body?: User[]) => void; status: (code: number) => HttpStatus; }' is missing the following properties from type 'Response': sendStatus, links, send, jsonp, and 78 more. How would u avoid including the all the parameters of Response ?Re
@Re do you have a solutin on thatIsotropic
have you tried wrapping your mocked object with as Response so TypeScript won't cry about the missing props you eventually don't need for your test purposes ?Saddle
I was running into this as well, if you type the partial as as any as Response then you will get the type errors to go away. Got that from #57964799Epaulet

© 2022 - 2024 — McMap. All rights reserved.