nest icon indicating copy to clipboard operation
nest copied to clipboard

OverrideMiddleware is missing

Open vanhumbeecka opened this issue 5 years ago • 4 comments

Feature Request

Is your feature request related to a problem? Please describe.

While trying to write tests for NestJS, I'm trying to override nestjs-middleware, by defining it with overrideProvider(MyMiddleware).useValue({ use(req, res, next) { next() }). This doesn't work however, since he just keeps dropping in the 'real' middleware (MyMiddleware) instead of the overridden one.

Describe the solution you'd like

Just like overrideProvider or overrideInterceptor, it would be useful to have a similar overrideMiddleware function for testing purposes.

What is the motivation / use case for changing the behavior?

Better/easier testability of NestJs applications!

vanhumbeecka avatar Feb 17 '20 09:02 vanhumbeecka

For anyone looking for a workaround - you can simply override the module class and provide a different configure() method

kamilmysliwiec avatar Mar 10 '20 09:03 kamilmysliwiec

I didn't get your comment @kamilmysliwiec , like for below example how can i do ?

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    })
      .overrideInterceptor(AuthMiddleware)
      .useValue({ use: (req: Request, _: Response, next: NextFunction) => next() })
      .compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

tkskumar avatar Feb 01 '21 12:02 tkskumar

@tkskumar would be something like this:

app.e2e-spec.ts
describe('AppController (e2e)', () => {
  let app: INestApplication

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

    app = moduleRef.createNestApplication()

    const appModule = app.get(AppModule)
    appModule.configure = function(consumer: MiddlewareConsumer) {
      consumer
        .apply((req, res, next) => { next(); }) // <<<<<<<
        .forRoutes('bar')
    }

    await app.init()
  })
})

Or you could use manual mocks from Jest to override the middleware class, like:

Full working example

Given the following project

.
├── nest-cli.json
├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── main.ts
│   ├── __mocks__
│   │   └── my.middleware.ts
│   └── my.middleware.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json
src/app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'
import { AppController } from './app.controller'
import { MyMiddleware } from './my.middleware'

@Module({
  controllers: [AppController],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(MyMiddleware)
      .forRoutes('bar')
  }
}
src/my.middleware.ts
import { NestMiddleware } from '@nestjs/common'
export class MyMiddleware implements NestMiddleware {
  use(_, __, next) {
    console.time('request took')
    next();
    console.timeEnd('request took')
  }
}
src/__mocks__/my.middleware.ts
import { NestMiddleware } from '@nestjs/common'
export class MyMiddleware implements NestMiddleware {
  use(_, __, next) {
    console.time('Looks like the request took')
    next();
    console.timeEnd('Looks like the request took')
  }
}
src/app.controller.ts
import { Controller, Get } from '@nestjs/common'

@Controller()
export class AppController {
  @Get('foo')
  getFoo() {
    return 'foo'
  }
  @Get('bar')
  getBar() {
    return 'bar'
  }
}
test/app.e2e-spec.ts
import * as request from 'supertest'
import { Test } from '@nestjs/testing'
import { AppModule } from './../src/app.module'
import { INestApplication } from '@nestjs/common'

jest.mock('../src/my.middleware.ts')

describe('AppController (e2e)', () => {
  let app: INestApplication

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

    app = moduleRef.createNestApplication()

    await app.init()
  });

  it('GET /foo', () => {
    return request(app.getHttpServer())
      .get('/foo')
      .expect(200)
      .expect('foo')
  })

  it('GET /bar', () => {
    return request(app.getHttpServer())
      .get('/bar')
      .expect(200)
      .expect('bar')
  })

  afterAll(async () => {
    await app.close()
  })
})

micalevisk avatar Dec 22 '21 02:12 micalevisk

Hi everyone, good afternoon.

I'm working on that to add this functionality to the current testing module capabilities.

leonardovillela avatar Dec 25 '21 13:12 leonardovillela

@micalevisk is all that boilerplate actually required? Couldn't one get away with: app.get(AppModule).configure = () => {} ?

fauxbytes avatar Nov 08 '22 15:11 fauxbytes

is all that boilerplate actually required?

There is no difference between yours and mine, you've just removed that temporary variable.

micalevisk avatar Nov 08 '22 15:11 micalevisk

Hi everyone,

I implemented the desired overrideMiddleware method in #12541! Maybe some maintainer (cc @kamilmysliwiec) can take a look at it.

schiemon avatar Oct 09 '23 14:10 schiemon

Let's track this here https://github.com/nestjs/nest/pull/12541

kamilmysliwiec avatar Oct 23 '23 08:10 kamilmysliwiec