Nest.js handling errors for HttpService

Viewed 5021

I'm trying to test NestJS's built in HttpService (which is based on Axios). I'm having trouble testing error/exception states though. In my test suite I have:

let client: SomeClearingFirmClient;

  const mockConfigService = {
    get: jest.fn((type) => {
      switch(type) {
        case 'someApiBaseUrl': {
          return 'http://example.com'
        }
        case 'someAddAccountEndpoint': {
          return '/ClientAccounts/Add';
        }
        case 'someApiKey': {
          return 'some-api-key';
        }

        default:
          return 'test';
      }
    }),
  };

  const successfulAdd: AxiosResponse = {
    data: {
      batchNo: '39cba402-bfa9-424c-b265-1c98204df7ea',
      warning: '',
    },
    status: 200,
    statusText: 'OK',
    headers: {},
    config: {},
  };

  const failAddAuth: AxiosError = {
    code: '401',
    config: {},
    name: '',
    message: 'Not Authorized',
  }

  const mockHttpService = {
    post: jest.fn(),
    get: jest.fn(),
  }

  it('Handles a failure', async () => {
    expect.assertions(1);
    mockHttpService.post = jest.fn(() => of(failAddAuth));

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        {
          provide: ConfigService,
          useValue: mockConfigService,
        },
        {
          provide: HttpService,
          useValue: mockHttpService,
        },
        SomeClearingFirmClient,
      ],
    }).compile();

    client = module.get<SomeClearingFirmClient>(SomeClearingFirmClient);

    const payload = new SomeClearingPayload();
    try {
      await client.addAccount(payload);
    } catch(e) {
      console.log('e', e);
    }
  });

And my implementation is:

async addAccount(payload: any): Promise<SomeAddResponse> {
    const addAccountEndpoint = this.configService.get('api.someAddAccountEndpoint');
    const url = `${this.baseUrl}${addAccountEndpoint}?apiKey=${this.apiKey}`;
    const config = {
      headers: {
        'Content-Type': 'application/json',
      }
    };

    const response = this.httpService.post(url, payload, config)
      .pipe(
        map(res => {
          return res.data;
        }),
        catchError(e => {
          throw new HttpException(e.response.data, e.response.status);
        }),
      ).toPromise().catch(e => {
        throw new HttpException(e.message, e.code);
      });

    return response;
  }

Regardless of whether I use Observables or Promises, I can't get anything to catch. 4xx level errors sail on through as a success. I feel like I remember Axios adding some sort of config option to reject/send an Observable error to subscribers on failures... but I could be imagining that. Am I doing something wrong in my test harness? The other StackOverflow posts I've seen seem to say that piping through catchError should do the trick, but my errors are going through the map operator.

1 Answers

Your mockHttpService seems to return no error, but a value:

mockHttpService.post = jest.fn(() => of(failAddAuth));

What of(failAddAuth) does is to emit a value(failAddAuth) and then complete.

That's why the catchError from this.httpService.post(url, payload, config) will never be reached, because no errors occur.

In order to make sure that catchError is hit, the observable returned by post() must emit an error notification.

You could try this:

// Something to comply with `HttpException`'s arguments
const err = { response: 'resp', status: '4xx' };

mockHttpService.post = jest.fn(() => throwError(err));

throwError(err) is the same as new Observable(s => s.error(err))(Source code).

Related