'Testing interceptors in nest.js won't wait for the previous test to finish with mocha as test runner

So I've written a LoggingInterceptor for a Nest.js project.

This is my implementation

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  constructor(private readonly logger: LoggingService) {}
  intercept(
    context: ExecutionContext,
    next: CallHandler<any>,
  ): Observable<any> {
    // Timestamp from start of request so we can calculate duration
    const reqStartTime = getNanoSecTime()

    const httpContext = context.switchToHttp()
    const request = httpContext.getRequest<Request>()

    return next.handle().pipe(
      // If the response is successful, we'll log the HTTP response
      map((data) => {
        const response = httpContext.getResponse<Response>()

        const logData = LoggingService.getLogData(
          reqStartTime,
          request,
          response.statusCode,
        )

        this.logger.log('HTTP response completed', logData)
        return data
      }),
      // If there is an error in the handler, we'll log the
      // HTTP response with additional error metadata
      catchError((err) => {
        let statusCode: number

        if (err instanceof HttpException) {
          statusCode = err.getStatus()
        } else {
          statusCode = 500
        }
        console.log('statusCode', statusCode)
        const logData = LoggingService.getLogData(
          reqStartTime,
          request,
          statusCode,
        )

        this.logger.error('Error in handler', err, logData)

        // We'll rethrow the error to be caught by Nest's built-in Exception Filter
        throw err
      }),
    )
  }
}


And this is my test setup

const mockLoggingService = sinon.createStubInstance(LoggingService)
const getLogDataStub = sinon
  .stub(LoggingService, 'getLogData')
  .returns(mockLogData)

const mockContext = {
  switchToHttp: () => ({
    getRequest: () => mockRequest,
    getResponse: () => mockResponse,
  }),
} as ExecutionContext

describe('LoggingInterceptor', () => {
  let interceptor: LoggingInterceptor

  beforeEach(() => {
    interceptor = new LoggingInterceptor(mockLoggingService)
  })

  afterEach(() => sinon.restore())

  it('call LoggingService.log with correct arguments', (done) => {
    const mockHandler: CallHandler = {
      handle: () => of([]),
    }

    interceptor.intercept(mockContext, mockHandler).subscribe({
      next: () => {
        expect(getLogDataStub.calledOnce).to.be.true
        expect(mockLoggingService.log.calledOnce).to.be.true
        expect(mockLoggingService.log.args[0][0]).to.equal(
          'HTTP response completed',
        )
        expect(mockLoggingService.log.args[0][1]).to.equal(mockLogData)
      },
      complete: () => {
        done()
      },
    })
  })

  it('should call LoggingService.error and handle when err is instance of HttpException', (done) => {
    const error = new HttpException('Bad Gateway', 502)
    const mockHandler: CallHandler = {
      handle: () => throwError(() => error),
    }

    interceptor.intercept(mockContext, mockHandler).subscribe({
      error: () => {
        expect(getLogDataStub.calledOnce).to.be.true
        expect(getLogDataStub.args[0][2]).to.equal(502)

        expect(mockLoggingService.error.calledOnce).to.be.true
        expect(mockLoggingService.error.args[0][0]).to.equal('Error in handler')
        expect(mockLoggingService.error.args[0][1]).to.equal(error)
        expect(mockLoggingService.error.args[0][2]).to.equal(mockLogData)

        done()
      },
    })
  })

I'm pretty new to Observables, so I'm really not sure why my test suite keeps on failing. Every test passes if run individually, but there is evidently some sort of memory leak occurring when running all tests, as the statusCode from the first test leaks into the statusCode for the 2nd and 3rd tests, always causing my assertions for the last tests to fail.



Solution 1:[1]

Well, it turns out the problem with my test had nothing to do with Observables and everything to do with where I was initializing my stubs.

If anyone's curious, I had to move my mockLoggingService into a beforeEachHook to make sure a new instance was being created before each test.

describe('LoggingInterceptor', () => {
  let interceptor: LoggingInterceptor
  let mockLoggingService

  // originally, my mockLoggingService was being declared outside my test block
  beforeEach(() => {
    mockLoggingService = sinon.createStubInstance(LoggingService)
    interceptor = new LoggingInterceptor(mockLoggingService)
  })

  // 

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Seanyboy Lee