Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP mock server ideas #75

Closed
diego-aquino opened this issue Feb 17, 2024 · 1 comment
Closed

HTTP mock server ideas #75

diego-aquino opened this issue Feb 17, 2024 · 1 comment
Assignees
Labels
planning Planning/discussion question Further information is requested
Milestone

Comments

@diego-aquino
Copy link
Member

diego-aquino commented Feb 17, 2024

Context

Zimic currently only applies HTTP mocks to the process where the interceptor is called. This makes it unusable in multi-process scenarios.

An example that illustrates this case:

  • A route handler of a Next.js application is being tested using Vitest.
  • The Next.js server was started as a separate process using next dev.
  • The Vitest tests make HTTP requests to the route handler running on localhost.
  • The route handler access an external service. This service is not guaranteed to be running all the time and, as we have no control of its availability, the tests should not reach it directly.

This is a great use case for Zimic, since mocking external applications with type safety is one of our goals. However, this scenario is not currently supported. For example, a test of this application could look like:

  1. The Vitest test initializes an interceptor and applies a mock to the external service. It is useful to keep the interceptor mock declarations in the test to easily simulate behaviors.
  2. The route handler running on the second process is not affected and requests makes by the Vitest test to the route handler still reach the external service.
Archived proposals

Proposal 1: mocks applied directly to the remote process

Solution

A feature that would extend Zimic to this use case would be the introduction of "remote HTTP interceptors". Remote interceptors are similar to our current interceptors, but they apply mocks to remote processes instead of the one the interceptor is initialized.

Using the example above, the workflow using remote interceptors would be like:

  1. The Next.js application would expose an endpoint (e.g. /zimic), available only in development and testing. This endpoint would be used by Zimic to receive mocks definitions and apply them to the Next.js process.
  2. The Vitest test would create a remote interceptor, linking it to the endpoint made available by the Next.js application.
  3. When applying mocks or doing any operation on the remote interceptor, a request would be issued to the endpoint on the Next.js application. In other words, declaring a mock in the Vitest test would actually send it to the Next,js application, where it would be applied. This would mean that all methods of remote interceptors and request trackers would need to be asynchronous.
  4. The external service would no longer be reached in tests, as the Vitest test would be able to apply mocks remotely to the server application.

API

// creates a RemoteHttpInterceptor
const remoteInterceptor = createHttpInterceptor<{ ... }>({
  type: 'remote' // 'local' is the default
  remoteURL: 'http://localhost:3000/zimic',
})

const getTracker = await remoteInterceptor.get('/').respond({
  status: 200,
  body: { success: true }
})

// mock applied to remote application

const getRequests = await getTracker.requests()
expect(getRequests).toHaveLenght(1)

Proposal 2: Zimic mock server

Solution

A feature that would extend Zimic to this use case would be the introduction of remote HTTP interceptors. Remote interceptors are similar to our current interceptors, but they apply mocks to a remote Zimic server process, instead of the process where the interceptor is initialized.

Using the example above, the workflow using remote interceptors would be like:

  1. In development or testing, a Zimic mock server would need to be started alongside the Next.js application. The Zimic server would be used to receive and apply mock definitions.
  2. The Next.js application would route requests intent to the external service to the Zimic server.
  3. The Vitest test would create a remote worker and interceptors, linking them to the Zimic mock server in use by the Next.js application.
  4. When doing any operation on the remote interceptor, such as declaring mocks, the operation would be sent to the Zimic server to apply it. In other words, declaring a mock in the Vitest test would actually send it to the Zimic server. This would allow simulating scenarios of the external service responses in tests. For this to work, all methods of remote interceptors and request trackers would need to be asynchronous.
  5. The external service would no longer be reached in tests, as the Vitest test would be able to apply mocks remotely to the Zimic mock server in use by the Next.js application. 🎉

API examples

Using local workers and interceptors:
type InterceptorSchema = HttpInterceptorSchema.Root<{
  '/': {
    GET: {
      response: {
        200: { body: { success: true } };
      };
    };
  };
}>;

const localWorker = createWorker({
  type: 'local',
});

await localWorker.start(); // starts the local worker, applies the mocks to the local process

// creates an HttpInterceptor
const localInterceptor = createHttpInterceptor<InterceptorSchema>({
  worker: localWorker,
  baseURL: 'http://localhost:3000',
});

// applying remote mocks is a asynchronous operation
const getTracker = localInterceptor.get('/').respond({
  status: 200,
  body: { success: true },
});

// mock applied to local process

const getRequests = getTracker.requests();
expect(getRequests).toHaveLength(1);
Using remote workers and interceptors:
type InterceptorSchema = HttpInterceptorSchema.Root<{
  '/': {
    GET: {
      response: {
        200: { body: { success: true } };
      };
    };
  };
}>;

const remoteWorker = createWorker({
  type: 'remote',
  serverURL: 'http://localhost:3001', // the URL of the zimic mock server
});

// start the zimic mock server with:
// zimic server start --port 3001 --on-ready 'npm test' --ephemeral true

await remoteWorker.start(); // starts the remote worker and links it to the zimic mock server

// creates a RemoteHttpInterceptor
const remoteInterceptor = createHttpInterceptor<InterceptorSchema>({
  worker: remoteWorker,
  // the application should use the base URL of the external service as http://localhost:3001/service1
  pathPrefix: '/service1',
});

// applying remote mocks is an asynchronous operation
const getTracker = await remoteInterceptor.get('/').respond({
  status: 200,
  body: { success: true },
});

// mock applied to remote server:
// any requests to http://localhost:3001 will be handled by the zimic mock server with the mock applied

// getting requests is also an asynchronous operation
const getRequests = await getTracker.requests();
expect(getRequests).toHaveLength(1);
@diego-aquino diego-aquino added question Further information is requested planning Planning/discussion labels Feb 17, 2024
@diego-aquino diego-aquino self-assigned this Feb 17, 2024
@diego-aquino diego-aquino changed the title Remote HTTP interceptors Remote HTTP interceptors ideas Feb 20, 2024
@diego-aquino
Copy link
Member Author

diego-aquino commented Feb 20, 2024

We will go ahead with "Proposal 2: Zimic mock server": #83

@diego-aquino diego-aquino modified the milestones: v0.6.0, v0.3.0 Feb 20, 2024
@diego-aquino diego-aquino modified the milestones: v0.3.0, v0.4.0 Feb 28, 2024
@diego-aquino diego-aquino changed the title Remote HTTP interceptors ideas HTTP mock server ideas Mar 12, 2024
@diego-aquino diego-aquino mentioned this issue Mar 12, 2024
36 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
planning Planning/discussion question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant