Nest: E2E tests fail when using exception filter extended from BaseExceptionFilter

Created on 3 Oct 2018  路  10Comments  路  Source: nestjs/nest

I'm submitting a...


[ ] Regression 
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

Setting up Exception Filter by extending from BaseExceptionFilter will fail E2E tests setup using a testing module.

I've been trying to set up an exception filter on the controller to map domain-level errors to their HttpException counterparts.

I set up the exception filter as follows:

@Catch()
export class HttpExceptionFilter extends BaseExceptionFilter {
  constructor(@Inject(HTTP_SERVER_REF) applicationRef: HttpServer) {
    super(applicationRef);
  }

  catch(error: Error, host: ArgumentsHost) {
    switch (error.name) {
      case 'UserNotFoundError':
        return super.catch(new BadRequestException('User not found'), host);

      default:
        return super.catch(error, host);
    }
  }
}

and introduced it into the controller:

  @Get()
  @UseFilters(HttpExceptionFilter)
  root(): string {
    throw new UserNotFoundError();
  }

This solution works on the actual server but fails during E2E tests with the follwing error:

    TypeError: this.applicationRef.reply is not a function

      17 |     switch (error.name) {
      18 |       case 'UserNotFoundError':
    > 19 |         return super.catch(new BadRequestException('User not found'), host);
         |                           ^
      20 | 
      21 |       default:
      22 |         return super.catch(error, host);

Expected behavior


It should be possible to extend BaseExceptionFilter and test its functionality.

Minimal reproduction of the problem with instructions


https://github.com/mpontus/nest-exception-filter-example

  1. Clone the repo
  2. Run yarn install
  3. Run yarn test:e2e to observe the error
  4. (optional) Run yarn start and visit http://localhost:3000 to observe correct behavior

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

My initial thought when coming up with this solution, was to rethrow errors from the custom exception filter. Similar approach was tested by @quezak in this issue, but the error shown there still persists.

My understandign from the linked PR is that subclassing BaseExceptionFilter is the recommended solution for this problem. If I made a mistake somewhere in my implementation, I would appreciate the correction.

Otherwise, I think it would be important to ensure that error handling, achieved through the use of exception filter, can be expressed through the context of E2E tests.

Environment


Nest version: 5.3.10


For Tooling issues:
- Node version: 8.11.3  
- Platform: Linux 


Others:

core done 馃憦 potential issue 馃挃

Most helpful comment

Hi! I've started getting this error for some reason on all my E2E tests with Nest 6. It used to work until 2-3 weeks ago. I did not change anything...

This is how my code looks like.

describe('E2E - authorization flows', () => {
  let app: INestApplication;
  beforeAll(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();
    app = moduleFixture.createNestApplication();
    await app.init();
  }, 15000);

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

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });

All 10 comments

I have just encountered this issue. I believe it may have something to do with how superagent sets up the instance, when you do request(app).get('/')....
If you extend BaseExceptionFilter, you need to inject HTTP_SERVER_REF, but it's an empty object (checked by putting breakpoints in). Hence the error.

constructor(@Inject(HTTP_SERVER_REF) appRef: HttpServer) {
    super(appRef); // appRef is {}
}

catch(exception: any, host: ArgumentsHost) {
    if (exception instanceof DomainError) {
        exception = new HttpException(exception.message, 400);
    }
    super.catch(exception, host); // this.appRef is {}
}

I don't know what to make of it, but posting just in case it helps anyone.

I worked around the problem by using NestFactory instead of createTestingModule:

import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

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

  beforeAll(async () => {
    app = await NestFactory.create(AppModule, {
      logger: false,
    });

    await app.init();
  });

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(400)
      .expect(/User not found/);
  });
});

Although, I think you lose the benefit of being able to substitute modules.

Should be fixed in 5.4.1, just ensure to entirely remove this:

constructor(@Inject(HTTP_SERVER_REF) appRef: HttpServer) {
    super(appRef); // appRef is {}
}

@kamilmysliwiec Confirmed, removing the constructor fixed the problem.

I'm not sure why, but in v5.7.3 I'm trying:

`

\@Catch(NotFoundException)
export class NotFoundExceptionFilter extends BaseExceptionFilter {
    catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const res: Response = ctx.getResponse();
        const req: Request = ctx.getRequest();

        if (req.url.indexOf('/api') == 0)
            super.catch(exception, host);
        else
            res.sendFile(join(__dirname, '../../public/index.html'));
    }
}

and applicationRef still undefined in the BaseExceptionFilter, causing UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'reply' of undefined

What am I missing?

@nelsonec87 the exception filter has access to the applicationRef only if the class instance has been instantiated by the framework (new NotFoundExceptionFilter() will not work).

That was it.
I removed the .useGlobalFilters and added the Filter to the main module's providers with APP_FILTER. It's working, thanks!

Hi! I've started getting this error for some reason on all my E2E tests with Nest 6. It used to work until 2-3 weeks ago. I did not change anything...

This is how my code looks like.

describe('E2E - authorization flows', () => {
  let app: INestApplication;
  beforeAll(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();
    app = moduleFixture.createNestApplication();
    await app.init();
  }, 15000);

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

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });

the exception filter has access to the applicationRef only if the class instance has been instantiated by the framework (new NotFoundExceptionFilter() will not work).

But how should one proceed if they want to fallback to the base exception filter inside a global application filter on certain conditions?

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

marshall007 picture marshall007  路  3Comments

breitsmiley picture breitsmiley  路  3Comments

VRspace4 picture VRspace4  路  3Comments

menme95 picture menme95  路  3Comments

KamGor picture KamGor  路  3Comments