Examining Axios HttpService piping through a NestJS middleware in a unit test

A middleware function retrieves a JSON document from a microservice endpoint and appends it to the request.

The good path test is successful, but I'm struggling to make the bad path test throw a ForbiddenException and stop it from invoking next().

When not in a Jest test environment, the middleware will block a request if it fails to fetch the JSON document.

Any assistance would be greatly appreciated :)

Middleware:

import { ForbiddenException, Injectable, NestMiddleware } from '@nestjs/common'
import { HttpService } from '@nestjs/axios'
import { NextFunction, Request, Response } from 'express'
import { catchError, firstValueFrom } from 'rxjs'
import { LoggerService } from '../logger.service'

@Injectable()
export class MyMiddleware implements NestMiddleware {
  constructor(
    private logger: LoggerService,
    private httpService: HttpService,
  ) {}

  async use(req: Request, res: Response, next: NextFunction) {

    const response = await firstValueFrom(
      this.httpService
        .get('https://myservice/document-endpoint', {
          headers: {
            Cookie: req.headers['cookie'],
          },
        })
        .pipe(
          catchError((error: Error) => {
            this.logger.error(error.message);
            throw new ForbiddenException()
          }),
        ),
    )

    req.document = response.data.value
    next()
  }
}

Tests:

The issue lies with the 'Bad path' test... the nextFunction method continues to get called and I can't figure out how to trigger the error.

import { MyMiddleware } from './mymiddleware'
import { NextFunction, Request, Response } from 'express'
import { LoggerService } from '../logger/logger.service'
import { of } from 'rxjs'
import { createMock } from '@golevelup/nestjs-testing'
import { HttpService } from '@nestjs/axios'
import { AxiosResponse } from 'axios'

describe('Authorization middleware', () => {
  let middleware: MyMiddleware
  let mockRequest: Partial<Request>
  let mockResponse: Partial<Response>
  let nextFunction: NextFunction = jest.fn()
  let mockHttpService = createMock<HttpService>()
  
  mockRequest = {
    headers: {
      cookie: 'idToken=asdasd'
    }

  beforeEach(async () => {
    mockResponse = {
      json: jest.fn(),
    }

    middleware = new MyMiddleware(new LoggerService(), mockHttpService)
  })

  test('bad path', async () => {
    jest.clearAllMocks()

    const mockDocumentResponse: AxiosResponse = {
      status: 404, 
      statusText: '',
      headers: {},
      config: {},
      data: { error: 'Not Found' }
    }

    const httpSpy = jest.spyOn(mockHttpService, 'get')
      .mockReturnValue(of(mockDocumentResponse))

    await middleware.use(mockRequest as Request, mockResponse as Response, nextFunction)
    expect(nextFunction).toBeCalledTimes(0)
  })


  test('good path', async () => {
    jest.clearAllMocks()

    const mockDocumentResponse: AxiosResponse = {
      status: 200, 
      statusText: '',
      headers: {},
      config: {},
      data: {
        value: 'example document',
      }
    }

    jest.spyOn(mockHttpService, 'get').mockImplementationOnce(() => of(mockDocumentResponse));
    await middleware.use(mockRequest as Request, mockResponse as Response, nextFunction)
    expect(nextFunction).toBeCalledTimes(1)
  })

})

Answer №1

Although I never got around to sharing the solution before, given the number of views this question receives, I believe it's worth posting now.

An essential aspect to note is that NestJS treats RxJS as a core component and fully embraces the concept of Observables. The HttpService, which acts as a wrapper for axios, typically returns Observables.

Observables function differently from promises (you can learn more at https://rxjs.dev/guide/observable). In the middleware code, handling this distinction involves using the firstValueFrom method imported from RxJS. This method converts the Observable into a standard JavaScript promise.

So, when testing the 'bad path,' we simulate an error scenario using Observables. Achieving this involves returning a new Observable that promptly throws an error by utilizing the s.error(err) method.

import { Observable, of } from 'rxjs'
...

test('bad path', async () => {
  jest.clearAllMocks()

  const err = { response: 'resp', status: '500' }

  // Instead of sending back an error response, we replicate throwing an error.
  const httpSpy = jest.spyOn(mockHttpService, 'get')
    .mockImplementationOnce(() => new Observable(subscriber => subscriber.error(err)))

  // Managing the anticipated ForbiddenException
  await middleware.use(mockRequest as Request, mockResponse as Response, nextFunction)
    .catch((error) => {
      expect(error).toBeInstanceOf(ForbiddenException)
      expect(nextFunction).toBeCalledTimes(0)
    })
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

"What is the best way to specify a type for the src attribute in a tsx file within a

<Image src= { sessionData?.user.image} alt="user" width={100} height={100} />` An issue has been encountered: There is a type error stating that 'string | null | undefined' cannot be assigned to type 'stri ...

Retrieving selected item values in Angular 2 using ng2-completer

Recently, I decided to experiment with a new autocompleter tool that is unfamiliar to me: https://github.com/oferh/ng2-completer. I successfully imported it and it seems to be functioning properly. My current goal is to retrieve the values of the selecte ...

What are the consequences of relying too heavily on deep type inference in React and Typescript?

In the process of migrating my React + Javascript project to Typescript, I am faced with preserving a nice unidirectional flow in my existing code. The current flow is structured as follows: 1. Component: FoobarListComponent -> useQueryFetchFoobars() 2 ...

Utilize Typescript to expand the functionality of the Express Request object

Is there a way to add a custom property to the request object in Express middleware using TypeScript without resorting to bracket notation? I am struggling to find a solution that satisfies this requirement. I would ideally like to achieve something like ...

Testing the activated lifecycle in jest can be done by following a few simple steps

I'm facing an issue with testing the activated lifecycle in my Vue file. The code snippet I have is as follows: activated(): void { this.name = “”; this.emailAddress = “”; this.birthdate = “”; } I want to create a Jest test that checks if ...

Learn the process of extracting an array of objects by utilizing an interface

Working with an array of objects containing a large amount of data can be challenging. Here's an example dataset with multiple key-value pairs: [{ "id": 1, "name":"name1", age: 11, "skl": {"name": & ...

Locate and retrieve the item that appears most often in a given array

In order to determine the mode of an array consisting of integer numbers only, I must create a function named findMode. If the array is empty, the function should return 0. Otherwise, it should return the element that occurs most frequently in the array. I ...

The inRequestScope feature seems to be malfunctioning and is not functioning as intended

Need help with using inRequestScope in inversifyJS For example: container.bind<ITransactionManager>(Types.MysqlTransactionManager).to(MysqlTransactionManager).inRequestScope() ... container.get<ITransactionManager>(Types.MysqlTransactionMana ...

What is the best way to selectively mock a single function within a module?

I am trying to mock only the bar function from the bar module, but it seems that when I run the test, Jest is also mocking the init function. Is there a way to specifically mock only the bar function without affecting other functions? // bar.ts: export co ...

Can a type be established that references a type parameter from a different line?

Exploring the Promise type with an illustration: interface Promise<T> { then<TResult1 = T, TResult2 = never>( onfulfilled?: | ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ...

Utilizing Node.js, Webpack, and TypeScript to gain access to the directory path of source files within a project, rather than the project

Just to clarify, I'm not looking for the process.cwd in this question, but I need to access the absolute path of the source project. For example: Source code: C:\Users\user1\projects\lib1\src\library.ts (which will beco ...

What is the purpose of exporting both a class and a namespace under the same name?

While exploring some code, I came across a situation where a class and a namespace with identical names were exported from a module. It seems like the person who wrote this code knew what they were doing, but it raised some questions for me. Could you shed ...

Idea fails to detect imports

I have been attempting to use Angular2 in IntelliJ IDEA IDE. Although my code is valid (I have tried compiling and executing it), the IDE keeps showing me this error: https://i.stack.imgur.com/w6wIj.jpg Is there a way to configure IntelliJ IDEA to hide t ...

Enhancing Luxon DateTime with extension type support

Referencing the issue at https://github.com/moment/luxon/issues/260, I am looking to extend the DateTime object as shown below: import { DateTime } from 'luxon'; function fromUnix(tsp?: number): DateTime { return DateTime.fromMillis(tsp * 1000 ...

XState: linking together multiple promises seamlessly without needing intermediate states

After reading the Invoking Multiple Services section, it seems that despite being able to invoke multiple promises, they appear to be triggered without waiting for the previous one to complete in my own tests. // ... invoke: [ { id: 'service1' ...

Is it possible to utilize Angular translate to support multiple languages for individual components or modules?

Utilizing ngx-translate to switch the language of my application has proven to be quite challenging. My application consists of different languages specifically for testing purposes and is divided into separate modules with lazy loading functionality. As ...

Utilizing the power of d3.js within Angular 4

Currently, I have successfully implemented code to draw a polygon using the mouse in a normal JavaScript file. Now, I am looking to replicate the same functionality in my TypeScript file. Below is an excerpt from my d3.js file: //D3.JS VERSION 3 //------ ...

The type 'myInterface' cannot be assigned to the type 'NgIterable<any> | null | undefined' in Angular

I am facing an issue that is causing confusion for me. I have a JSON data and I created an interface for it, but when I try to iterate through it, I encounter an error in my HTML. The structure of the JSON file seems quite complex to me. Thank you for yo ...

Typescript's Integrated Compatibility of Types

One important concept I need to convey is that if one of these fields exists, then the other must also exist. When these two fields are peers within the same scope, it can be challenging to clearly communicate this connection. Consider the example of defi ...

Refresh a doughnut chart in real-time using NG2Charts

Currently, I am in the process of developing a macronutrient calculator as part of a project. The idea is to have a form where users can input values, and a corresponding doughnut chart will display with initial values set at 0. However, upon clicking the ...