DEV Community

Cover image for Leveraging Mock Service Workers for NestJS e2e tests
Thiago Valentim
Thiago Valentim

Posted on

Leveraging Mock Service Workers for NestJS e2e tests

In the previous article, we discussed how almost any modern web application has to communicate with external systems. It's also safe to assume most of that communication is made via HTTP calls. In today's post, we'll learn how to:

  • Intercept and stub HTTP calls in NodeJS using Mock Service Workers and NestJS as the framework - No interfaces are needed this time.
  • Create reusable API stubs for any e2e test.
  • Simulate network errors.

Bonus:

  • How to transform the external data structure into domain objects using an anti-corruption layer (ACL).
  • Leveraging a Result class to separate application and network layer error handling.

As always, you can check out the reference repository for more details on the implementation.

ℹ️ TL:DR -> We utilize MSW to intercept NodeJS's internal request calls using the setupServer() function and a server.use() call for each test. You can check out the final code below:

import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { http, HttpResponse, passthrough } from 'msw';
import { setupServer, SetupServerApi } from 'msw/node';
import * as request from 'supertest';
import { stubGoogleAPIResponse } from '../src/address/application/test/google-geocode-mock.handler';
import { AppModule } from '../src/app.module';

describe('Get GeoCode Address', () => {
  let app: INestApplication;
  let mockServer: SetupServerApi;

  beforeAll(() => {
    // We must define a passthrough for localhost requests so MSW doesn't log a warning.
    mockServer = setupServer(http.all('http://127.0.0.1*', passthrough));
    mockServer.listen();
  });

  afterAll(() => {
    mockServer.close();
  });

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

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

  it('returns the coordinates for a valid address', () => {
    mockServer.use(
      stubGoogleAPIResponse({
        results: [
          {
            geometry: {
              location: {
                lat: 37.4224082,
                lng: -122.0856086,
              },
            },
          },
        ],
        status: 'OK',
      }),
    );

    return request(app.getHttpServer())
      .get(
        '/addresses/geo-code?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA',
      )
      .expect({
        latitude: 37.4224082,
        longitude: -122.0856086,
      });
  });

  it('returns a 404 error when the address is not found', async () => {
    mockServer.use(
      stubGoogleAPIResponse({
        results: [],
        status: 'ZERO_RESULTS',
      }),
    );

    return request(app.getHttpServer())
      .get('/addresses/geo-code?address=invalid+address')
      .expect(404)
      .expect({
        statusCode: 404,
        message: 'Address not found',
      });
  });
  it('returns a 424 error (code=01) when the API key is invalid', async () => {
    mockServer.use(
      stubGoogleAPIResponse({
        results: [],
        status: 'REQUEST_DENIED',
        error_message: 'The provided API key is invalid.',
      }),
    );

    return request(app.getHttpServer())
      .get('/addresses/geo-code?address=invalid+address')
      .expect(424)
      .expect({
        statusCode: 424,
        message: 'Failed to get coordinates',
        code: '01',
      });
  });
  it('returns a 424 error (code=02) when there is a network error', async () => {
    mockServer.use(stubGoogleAPIResponse(HttpResponse.error()));

    return request(app.getHttpServer())
      .get('/addresses/geo-code?address=invalid+address')
      .expect(424)
      .expect({
        statusCode: 424,
        message: 'Failed to get coordinates',
        code: '02',
      });
  });
});
Enter fullscreen mode Exit fullscreen mode

If you are interested in the process to get there, including the example use case, proceed with me to the following sections.

Feature

Let's consider a simple (but real-world-like) application to demonstrate MSW usage. That app returns the latitude and longitude of a given address the user inputs:

Three distinct frames showcasing an example

  • The user types a location in the search bar
  • The user clicks the button to "Get Coordinates"
  • The location's coordinates are shown along with the location.

The gherkin description below details each use case:

Feature: As a user I want to be able to see an address' cooridnates on the map.

  A user can enter an address to view their location on the map. The system
  returns either the coordinates of that address or an error when not found.

  Scenario: Successfully retrieve address coordinates
    Given the address '1600 Amphitheatre Parkway, Mountain View, CA' is valid with coordinates 37.4224082, -122.0856086
    When I search for that location's address
    Then I can see the address'coordinates

  Scenario: Handle non-existent address
    Given the address 'Invalid Address' is invalid
    When I search that location's address
    Then I receive an error message: 'Address not found'

  Scenario: Handle network error during coordinate retrieval
    Given a network error occurs
    When I search for the address 'Some Address'
    Then I see the error message: 'Failed to get coordinates'
Enter fullscreen mode Exit fullscreen mode

We'll also use Google's Geocoding API to retrieve a location's coordinates. We just need to send a GET request to the API endpoint, passing the address string and the API key:

ADDRESS="Germany"
API_KEY="MY-APY-KEY" 

curl -X GET "https://maps.googleapis.com/maps/api/geocode/json?address=${ADDRESS}&key=${API_KEY}"
Enter fullscreen mode Exit fullscreen mode

You'll need a Google Services API Key to fetch the real data, but fortunately, we don't even need to work with the API for our tests. We just need to know what its response looks like:

{
  "results": [
    {
      "address_components": [
        {
          "long_name": "Germany",
          "short_name": "DE",
          "types": [
            "country",
            "political"
          ]
        }
      ],
      "formatted_address": "Germany",
      "geometry": {
        "bounds": {
          "south": 47.270114,
          "west": 5.8663425,
          "north": 55.0815,
          "east": 15.0418962
        },
        "location": {
          "lat": 51.165691,
          "lng": 10.451526
        },
        "location_type": "APPROXIMATE",
        "viewport": {
          "south": 47.270114,
          "west": 5.8663425,
          "north": 55.0815,
          "east": 15.0418962
        }
      },
      "place_id": "ChIJa76xwh5ymkcRW-WRjmtd6HU",
      "types": [
        "country",
        "political"
      ]
    }
  ],
 "status": "OK"
}
Enter fullscreen mode Exit fullscreen mode

ℹ️ Note: We'll consider the Geocode API always returns a single result for simplicity.

So, the data that really matters to us can be obtained from response.results[0].geometry.location. Let's keep that in mind when we start implementing the service. For now, let's note down a simplified type of that response:

export type GoogleGeocodeResponse = {
  results: {
    geometry: {
      location: {
        lat: number;
        lng: number;
      };
    };
  }[];
  status: string;
  error_message?: string; // This property exists when an error occurs.
};
Enter fullscreen mode Exit fullscreen mode

The error_message property determines when an application error occurs —yes, they send a 200 status even when there is an error.

Meme showing two guys in a classroom, one with the label

Now that we have everything we need to start writing the test cases, let's proceed to the next section.

Defining the e2e test cases

The first step is to write the test scenario descriptions with the .todo() suffix as we won't implement them just yet:

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
import { http, HttpResponse } from 'msw';
import { setupServer, SetupServerApi } from 'msw/node';

describe('Get GeoCode Address', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

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

  it.todo('returns the coordinates for a valid address');
  it.todo('returns a 404 error for an invalid address');
  it.todo('returns a 424 (code=01) error when the API key is invalid');  // Extra test for a technical edge case
  it.todo('returns a 424 (code=02) error when there is a network error');
});
Enter fullscreen mode Exit fullscreen mode

Notice that we have an extra test case above: returns a 424 (code=01) error when the API key is invalid. This is an unlikely scenario, but it was put there for a good reason - to simulate how we should handle unexpected errors.

If the API Key is invalid (or has expired), we want to associate a specific error code with it so the client side can report that via an automated channel. Moreover, we don't want to let the user know the underlying reason for failure, so the error message doesn't say anything about the API Key.

Writing the success test case

Let's start with the success scenario test case:

 it('returns the latitude/longitude for a valid address', () => {
    // How do we stub the geo-code API response? 🤔
    const expectedCoordinates = {
      latitude: 37.4224082,
      longitude: -122.0856086,
    }
    return request(app.getHttpServer())
      .get(
        '/addresses/geo-code?address=Germany',
      )
      .expect(200)
      .expect(expectedCoordinates);
  });
Enter fullscreen mode Exit fullscreen mode

The test is straightforward. We send a request to the GET /addresses/geocode endpoint and expect to get Germany's coordinates as a response. But since we won't connect to the real API, we must stub this response with MSW. To implement this, we must use MSW's NodeJS integration.

ℹ️ Note: MSW default mode works in the browser with Service Workers, so we need a slightly different route to make it work with NodeJS

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
import { http, HttpResponse } from 'msw';
import { setupServer, SetupServerApi } from 'msw/node';

describe('Get GeoCode Address', () => {
  let app: INestApplication;
  let mockServer: SetupServerApi;

  const handlers = [
    // Intercept "GET 'https://maps.googleapis.com/maps/api/geocode" requests...
    http.get(
      'https://maps.googleapis.com/maps/api/geocode/json',
      () => {
        // ...and respond to them using this JSON response.
        return HttpResponse.json(
          {
            results: [
              {
                geometry: {
                  location: {
                    lat: 37.4224082,
                    lng: -122.0856086,
                  },
                },
              },
            ],
            status: 'OK'
          },
        );
      },
    ),
  ];

  beforeAll(() => {
    mockServer = setupServer(...handlers);
    mockServer.listen();
  });

  afterAll(() => {
    mockServer.close();
  });

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

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

  it('returns the coordinates for a valid address', () => {
    const expectedCoordinates = {
      latitude: 37.4224082,
      longitude: -122.0856086,
    }
    return request(app.getHttpServer())
      .get(
        '/addresses/geo-code?address=Germany',
      )
      .expect(200)
      .expect(expectedCoordinates);
  });
});
Enter fullscreen mode Exit fullscreen mode

The key steps to setup MSW above are:

  • Define the request handler array containing the handler that should respond to HTTP calls made to the geocode API
  • Setup the server with the list of handlers.
  • Make the server start listening
  • Ensure we close it in the afterAll block

Now, we must implement the controller and the service that should handle this request. We'll communicate with our Geocode API using NestJS HttpModule.

Implementing the first version of Controller and Service

We can use NestJS CLI to create an 'AddressModule', an 'AddressController', and an 'AddressService' to scaffold their basic structure:

nest g module address
nest g controller address
nest g service address
Enter fullscreen mode Exit fullscreen mode

Then, implement the communication with the GeoCode API at the service layer:

import { Injectable } from '@nestjs/common';
import { Coordinates } from '../domain/coordinates';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

export type GoogleGeocodeResponse = {
  results: {
    geometry: {
      location: {
        lat: number;
        lng: number;
      };
    };
  }[];
};

@Injectable()
export class AddressService {
  constructor(private readonly httpService: HttpService) {}

  async getGeoCode(address: string): Promise<Coordinates> {
    const response = await firstValueFrom(
      this.httpService.get<GoogleGeocodeResponse>(
        `https://maps.googleapis.com/maps/api/geocode/json?key=test&address=${address}`,
      ),
    );

    const { lat, lng } = response.data.results[0].geometry.location;

    return new Coordinates(lat, lng);
  }
}
Enter fullscreen mode Exit fullscreen mode

Note that we're returning a domain object from that service: Coordinates. It represents a coordinate Value Object and should be responsible for validating itself. This centralizes the logic that defines what is a valid coordinate, making it reusable and safe to be used by other services:

export class Coordinates {
  public readonly latitude: number;
  public readonly longitude: number;

  constructor(lat: number, lng: number) {
    if (!Coordinates.isValid(lat, lng)) {
      throw new Error(`Invalid coordinates: lat=${lat}, lng=${lng}`);
    }

    this.latitude = lat;
    this.longitude = lng;
  }

  static isValid(lat: number, lng: number): boolean {
    return (
      typeof lat === 'number' &&
      typeof lng === 'number' &&
      lat >= -90 &&
      lat <= 90 &&
      lng >= -180 &&
      lng <= 180
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we must inject this AddressService into our AddressController:

import { Controller, Get, Query } from '@nestjs/common';
import { Coordinates } from './domain/coordinates';
import { AddressService } from './application/address.service';

@Controller('addresses')
export class AddressController {
  constructor(private readonly addressService: AddressService) {}

  @Get('geo-code')
  async getGeoCode(@Query('address') address: string): Promise<Coordinates> {
    return this.addressService.getGeoCode(address);
  }
}
Enter fullscreen mode Exit fullscreen mode

Lastly, let's update the AddressModule to import NestJS's HttpModule to provide the HttpService:

import { Module } from '@nestjs/common';
import { AddressController } from './address.controller';
import { AddressService } from './application/address.service';
import { HttpModule } from '@nestjs/axios';

@Module({
  imports: [HttpModule],
  controllers: [AddressController],
  providers: [AddressService],
})
export class AddressModule {}
Enter fullscreen mode Exit fullscreen mode

With all the pieces fitted, we can run the test case:

$ pnpm run test:e2e
 PASS  test/get-address-coordinates.e2e-spec.ts
Enter fullscreen mode Exit fullscreen mode

And then the first cycle is completed! Well, almost. If you execute the test command above, you'll also notice it logs a warning:

[MSW] Warning: intercepted a request without a matching request handler:

        • GET http://127.0.0.1:36245/addresses/geo-code?address=Germany
Enter fullscreen mode Exit fullscreen mode

So, how do we get rid of it?

Fixing MSW "intercepted request without a matching request handler" warning

The WARN happened because MSW detected we sent a request to a local server (hence the 127.0.0.1 IP address) but didn't register any handler. To solve this, we must define a passthrough handler because we don't want to change or stub responses from our own server:

  const handlers = [
    http.get('https://maps.googleapis.com/maps/api/geocode/json', () => HttpResponse.json(geocodeResponse),
    http.get('http://127.0.0.1*', passthrough),
  ];
Enter fullscreen mode Exit fullscreen mode

The message is gone now when running the e2e test cases:

 PASS  test/app.e2e-spec.ts
 PASS  test/get-address-coordinates.e2e-spec.ts

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.713 s, estimated 5 s
Ran all test suites.
------------------------
Enter fullscreen mode Exit fullscreen mode

Refactoring MSW response stubs to allow response per test case

Before implementing the subsequent test cases, which should handle error scenarios, we must refactor the existing code slightly because they are globally defined. That means any request to the maps/api/geocode/json endpoint produces the same success response.

Image depicting each test case sending a request to the MSW Server but receiving the same response

Each test case sends a request to MSW Server but gets the same response.

The alternative that allows us to define per-use-case response is MSW's use method. This method dynamically attaches request handlers to MSW's server, which can later be removed by calling resetHandlers(). Therefore, we'll implement three main changes to our test code:

  1. Remove the specific request handlers from the server setup.
  2. Add test-case-specific request handlers with mockServer.use()
  3. Extract the code that creates a request handler to a file next to the AddressSevice so we encapsulate its details.
import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { http, passthrough } from 'msw';
import { setupServer, SetupServerApi } from 'msw/node';
import * as request from 'supertest';
import { stubGoogleAPIResponse } from '../src/address/application/test/google-geocode-mock.handler';
import { AppModule } from '../src/app.module';

describe('Get GeoCode Address', () => {
  let app: INestApplication;
  let mockServer: SetupServerApi;

  beforeAll(() => {
    // We must define a passthrough for localhost requests so MSW doesn't log a warning.
    mockServer = setupServer(http.all('http://127.0.0.1*', passthrough));
    mockServer.listen();
  });

  afterAll(() => {
    mockServer.close();
  });

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

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

  it('returns the coordinates for a valid address', () => {
    mockServer.use(
      stubGoogleAPIResponse({
        results: [
          {
            geometry: {
              location: {
                lat: 37.4224082,
                lng: -122.0856086,
              },
            },
          },
        ],
        status: 'OK',
      }),
    );

    return request(app.getHttpServer())
      .get('/addresses/geo-code?address=Germany')
      .expect({
        latitude: 37.4224082,
        longitude: -122.0856086,
      });
  });
});
Enter fullscreen mode Exit fullscreen mode

And this is the stubGoogleAPIResponse() implementation code:

import { http, HttpResponse } from 'msw';
import { GoogleGeocodeResponse } from '../address.service';

export function stubGoogleAPIResponse(
  response: GoogleGeocodeResponse | Response,
) {
  return http.get(
    'https://maps.googleapis.com/maps/api/geocode/json',
    () =>
      response instanceof Response ? response : HttpResponse.json(response),
    {
      // Ensure this stubbed response is only used once.
      once: true,
    },
  );
}
Enter fullscreen mode Exit fullscreen mode

ℹ️ Note: The stub accepts an instance of Response as a possible input to simulate errors.

And that's all we needed to refactor at this point. In the next section we'll implement the error cases.

Implementing the error cases

There are three error cases we have to simulate now:

  1. Address not found. Response: { results: [], status: 'ZERO_RESULTS' }
  2. The API Key is invalid. Response: { results: [], status: 'REQUEST_DENIED' }
  3. There is a network error. In this case, the request is aborted and we don't receive a response from the server.

Let's write down each of these error cases:

 it('returns a 404 error when the address is not found', async () => {
    mockServer.use(
      stubGoogleAPIResponse({
        results: [],
        status: 'ZERO_RESULTS',
      }),
    );

    return request(app.getHttpServer())
      .get('/addresses/geo-code?address=invalid+address')
      .expect(404)
      .expect({
        statusCode: 404,
        message: 'Address not found',
      });
  });
 it('returns a 424 error (code=01) when the API key is invalid', async () => {
    mockServer.use(
      stubGoogleAPIResponse({
        results: [],
        status: 'REQUEST_DENIED',
        error_message: 'The provided API key is invalid.',
      }),
    );

    return request(app.getHttpServer())
      .get('/addresses/geo-code?address=invalid+address')
      .expect(424)
      .expect({
        statusCode: 424,
        message: 'Failed to get coordinates',
        code: '01',
      });
  });
  it('returns a 424 error (code=02) when there is a network error', async () => {
    mockServer.use(stubGoogleAPIResponse(HttpResponse.error()));

    return request(app.getHttpServer())
      .get('/addresses/geo-code?address=invalid+address')
      .expect(424)
      .expect({
        statusCode: 424,
        message: 'Failed to get coordinates',
        code: '02',
      });
  });
Enter fullscreen mode Exit fullscreen mode

The first two test cases are similar - they only need to specify the JSON response. The third test, however, has to respond with HttpResponse.error() to simulate a network error.

ℹ️ Note: You can also easily simulate responses with error status by passing a second argument to the HttpResponse.json() method in the stub handler. For instance: HttpResponse.json(response, { status: 404 })

Handling errors at the service and controller layer.

The last piece of the puzzle is handling each of these errors. To do so, we'll separate error handling into two layers:

  • Application Layer - The service takes the API response and verifies whether this is a success or an error result. Then, it returns that result to the layer above instead of throwing an error. We use the Result class for that purpose.
  • Network Layer - The controller takes the response from the service and decides how to response to each type of error - formatting the messages accordingly and specifying the response status.

The diagram below depicts the error-handling flow:

Diagram illustrating data flow between the controller, the service and the GeoCodeAPI. When an error occurs at the GeoCode API, the service wraps it in a Result class and returns that to the Controller. The Controller, then, determines what status code it should return based on the error code.

So let's start with the Service. We'll define a constant to hold all known error messages that Google's API responds so we can map them to specific Error classes:

export const GeoLocationErrorCode = {
  AddressNotFound: 'ADDRESS_NOT_FOUND',
  InvalidAPIKey: 'INVALID_API_KEY',
  UnknownException: 'UNKNOWN',
  NetworkException: 'NETWORK',
  InvalidRequest: 'INVALID_REQUEST',
} as const;

export class AddressNotFoundError extends ApplicationError {
  constructor() {
    super('Address not found', GeoLocationErrorCode.AddressNotFound);
  }
}

export class InvalidAPIKeyError extends ApplicationError {
  constructor() {
    super('Failed to get coordinates', GeoLocationErrorCode.InvalidAPIKey);
  }
}

export class GeoLocationNetworkError extends ApplicationError {
  constructor() {
    super('Failed to get coordinates', GeoLocationErrorCode.NetworkException);
  }
}

export class InvalidInputError extends ApplicationError {
  constructor() {
    super('Invalid input', GeoLocationErrorCode.InvalidRequest);
  }
}

export class UnknownGeolocationError extends ApplicationError {
  constructor() {
    super('Unknown error', GeoLocationErrorCode.UnknownException);
  }
}
Enter fullscreen mode Exit fullscreen mode

Then, we'll capture each possible error and map it to our application-level errors:

@Injectable()
export class AddressService {
  private readonly logger = new Logger(AddressService.name);
  constructor(private readonly httpService: HttpService) {}

  async getGeoCode(
    address: string,
  ): Promise<Result<Coordinates, ApplicationError | AxiosError>> {
    const response = await firstValueFrom(
      this.httpService
        .get<GoogleGeocodeResponse>(
          `https://maps.googleapis.com/maps/api/geocode/json?key=test&address=${address}`,
        )
        .pipe(
          catchError((error: AxiosError) => {
            if (error.response) {
              return of(error);
            }

            if (error.request) {
              return of(new GeoLocationNetworkError());
            }

            return of(new UnknownGeolocationError());
          }),
        ),
    );

    if (response instanceof Error) {
      return Result.fail(response);
    }

    const data = response.data;

    if (data.status !== 'OK') {
      this.logger.error(`Failed to get coordinates: ${data.error_message}`);
      switch (data.status) {
        case 'ZERO_RESULTS':
          return Result.fail(new AddressNotFoundError());
        case 'REQUEST_DENIED':
          return Result.fail(new InvalidAPIKeyError());
        case 'INVALID_REQUEST':
          return Result.fail(new InvalidInputError());
        default:
          return Result.fail(new UnknownGeolocationError());
      }
    }

    const { lat, lng } = data.results[0].geometry.location;

    return Result.ok(new Coordinates(lat, lng));
  }
}
Enter fullscreen mode Exit fullscreen mode

ℹ️ Note: You can check out the implementation details of the Result class in the repository code.

Finally, the AddressController decides how to map each type of error to a specific network exception:

import {
  Controller,
  Get,
  HttpException,
  NotFoundException,
  Query,
} from '@nestjs/common';
import { Coordinates } from './domain/coordinates';
import {
  AddressService,
  GeoLocationErrorCode,
} from './application/address.service';

@Controller('addresses')
export class AddressController {
  constructor(private readonly addressService: AddressService) {}

  @Get('geo-code')
  async getGeoCode(@Query('address') address: string): Promise<Coordinates> {
    const result = await this.addressService.getGeoCode(address);

    if (result.isOk()) {
      return result.value;
    }

    switch (result.error.code) {
      case GeoLocationErrorCode.AddressNotFound:
        throw new NotFoundException('Address not found', {
          cause: result.error,
        });
      case GeoLocationErrorCode.InvalidAPIKey:
        throw new HttpException(
          {
            statusCode: 424,
            message: 'Failed to get coordinates',
            code: '01',
          },
          424,
          {
            cause: result.error,
          },
        );
      case GeoLocationErrorCode.NetworkException:
        throw new HttpException(
          {
            statusCode: 424,
            message: 'Failed to get coordinates',
            code: '02',
          },
          424,
          {
            cause: result.error,
          },
        );
      case GeoLocationErrorCode.InvalidRequest:
        throw new HttpException(
          {
            statusCode: 424,
            message: 'Failed to get coordinates',
            code: '03',
          },
          424,
          {
            cause: result.error,
          },
        );
      default:
        throw result.error;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This finalizes our last cycle. Each error scenario is handled by the AddressService, turned into a Result object, and mapped to an HttpException at the AddressController. Running our e2e tests is the final litmus test that everythingworks as intended:

$ pnpm run test:e2e
 PASS  test/get-address-coordinates.e2e-spec.ts

Test Suites: 2 passed, 2 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        5.061 s
Ran all test suites.
Enter fullscreen mode Exit fullscreen mode

Review and Conclusion

Top comments (0)