#how do I mock a response in an interceptor?

7 messages · Page 1 of 1 (latest)

celest oxide
#

I have a very simple interceptor that redirects you when the app is in outage mode

@Injectable()
export class OutageRedirectInterceptor implements NestInterceptor {
  constructor(private readonly stateService: StateService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    if (this.stateService.isInOutageMode) {
      const res = context.switchToHttp().getResponse<Response>();
      res.redirect('/');
    }

    return next.handle();
  }
}

How do I write a unit test for this? I am having trouble mocking the nested context.switchToHttp().getResponse<Response>() call. Here's what I have so far

describe('OutageAccessInterceptor', () => {
  let interceptor: OutageRedirectInterceptor;
  let stateSvc: StateService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [],
      providers: [OutageRedirectInterceptor, StateService],
    }).compile();

    interceptor = module.get(OutageRedirectInterceptor);
    stateSvc = module.get(StateService);
  });

  it('should redirect when outage mode is ENABLED', () => {
    stateSvc.isInOutageMode = true;

    const executionCtxMock = mockDeep<ExecutionContext>();
    const redirectSpy = jest.spyOn(executionCtxMock.switchToHttp().getResponse<Response>(), 'redirect');
    //                                                            ^^^^^^^^^^^^
    //ERROR "Cannot read properties of undefined (reading 'getResponse')"

    const nextMock: CallHandler = {
      handle: jest.fn(() => of()),
    };

    interceptor.intercept(executionCtxMock, nextMock);

    expect(nextMock.handle).toHaveBeenCalledWith();
    expect(redirectSpy).toHaveBeenCalledWith('/');
  });
});
bleak tangle
#

I'd try to use @golevelup/ts-jest

celest oxide
#

I am already using jest-mock-extended will that not help here? I'd rather not add another testing library if I don't need it in my 200+ other tests

bleak tangle
#

got you

#

executionCtxMock.switchToHttp() is returning undefined right?

#

so you'll need to see how to fix that using that lib (mockDeep)

celest oxide
#

ok, so I think this works. it feels a bit hacky though

it('should redirect when outage mode is ENABLED', () => {
  stateSvc.isInOutageMode = true;

  const res = httpMock.createResponse();
  jest.spyOn(res, 'redirect');

  const executionCtxMock = mockDeep<ExecutionContext>();
  //@ts-expect-error  -- technically not a match for what TypeScript expects but we can force it
  jest.spyOn(executionCtxMock, 'switchToHttp').mockImplementation(() => ({
    getResponse: () => res,
  }));

  const nextMock: CallHandler = {
    handle: jest.fn(() => of()),
  };

  interceptor.intercept(executionCtxMock, nextMock);

  expect(nextMock.handle).toHaveBeenCalledWith();
  expect(res.redirect).toHaveBeenCalledWith('/');
});

it('should NOT redirect when outage mode is DISABLED', () => {
  stateSvc.isInOutageMode = false;

  const res = httpMock.createResponse();
  jest.spyOn(res, 'redirect');

  const executionCtxMock = mockDeep<ExecutionContext>();
  //@ts-expect-error -- Technically not a match for what TypeScript expects but we can force it
  jest.spyOn(executionCtxMock, 'switchToHttp').mockImplementation(() => ({
    getResponse: () => res,
  }));

  const nextMock: CallHandler = {
    handle: jest.fn(() => of()),
  };

  interceptor.intercept(executionCtxMock, nextMock);

  expect(nextMock.handle).toHaveBeenCalledWith();
  expect(res.redirect).not.toHaveBeenCalled();
});