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

Allow overriding entire handler function in generated MSW handlers, not just response object #1206

Open
severinh opened this issue Feb 6, 2024 · 4 comments
Labels
enhancement New feature or request mock Related to mock generation

Comments

@severinh
Copy link
Contributor

severinh commented Feb 6, 2024

This is a feature request, not a bug.

What are the steps to reproduce this issue?

#1186 made it possible to override response objects in MSW handlers. Thank you for that! Unfortunately, we often cannot use them in our project in many tests because we're missing two things:

  1. In unit tests, we often override the default handlers to simulate a HTTP 500 errors to test the request failure path.
  2. We sometimes create handlers where the output depends on the request.

Here's an example of a handler generated by Orval:

export const getProjectQueryMockHandler = (overrideResponse?: Result) => {
  return http.post('*/projects/:projectId', async () => {
    await delay(200);
    return new HttpResponse(JSON.stringify(overrideResponse ? overrideResponse : getProjectQueryMock()),
      {
        status: 200,
        headers: {
          'Content-Type': 'application/json',
        }
      }
    )
  })
}

Simulating HTTP errors in tests

Then, for example, a unit test needs to simulate a HTTP 500 error. It currently cannot use getProjectQueryMockHandler for that since that always uses HTTP 200. So we instead don't use any code generated by Orval, and write something like this. Unfortunately, this is a lot less type-safe, since there's nothing checking that the path projects/:projectId is correct.

  server.use(
    http.post('*/projects/:projectId', async () => new HttpResponse('', { status: 500 }))
  );

What we'd love to see is getProjectQueryMockHandler allowing callers to either override the response, or the entire handler function. Something like:

  server.use(
    getProjectQueryMockHandler(async () => new HttpResponse('', { status: 500 }))
  );

Alternative solution: Generate a function to get the path of the endpoint, such as:

  server.use(
    http.post(getProjectQueryPath(), async () => new HttpResponse('', { status: 500 }))
  );

Changing request based on the response

Similarly, we have tests where the MSW handlers need to return a response that depends on what is in the request. So we currently write something like:

  server.use(
    http.post('*/projects/:projectId', async ({ request }) => {
      const input = (await request.json()) as Input;
      // Return a response based on input.
  })),

Ideally, we could write this as:

  server.use(
    getProjectQueryMockHandler(async ({ request }) => {
      const input = (await request.json()) as Input;
      // Return a response based on input.
  }));

Any other comments?

Allowing overriding of the entire handler function is also what @graphql-codegen/typescript-msw offers, which we use for our GraphQL endpoints.

Still, orval is great! I expect it to be a significant boost in DevEx for our team.

What versions are you using?

Package Version: 6.24.0

@soartec-lab soartec-lab added enhancement New feature or request mock Related to mock generation labels Feb 6, 2024
@soartec-lab
Copy link
Collaborator

@severinh

Thank you for made this issue.

Simulating HTTP errors in tests

Regarding alternatives, when extending and defining a mock definition in a test, for example, you can use the following function that automatically generates the key.

https://github.com/anymaniax/orval/blob/master/samples/react-app-with-swr/src/api/endpoints/petstoreFromFileSpecWithTransformer.ts#L35


const key = getListPetsKey()

server.use(
  http.post(key, async () => new HttpResponse('', { status: 500 }))
);

@severinh
Copy link
Contributor Author

Regarding alternatives, when extending and defining a mock definition in a test, for example, you can use the following function that automatically generates the key.

Thanks for your reply!

The get*Key methods could be used, but there a number of downsides to that:

  1. The method actually returns an array, where only the first element is the URL. So you would need to do something like http.post(getListPetsKey(123)[0], async () => new HttpResponse('', { status: 500 }))
  2. You still have to repeat the HTTP verb (e.g. http.post). That's a possible source of errors, since the writer of the test has to manually make sure to pick the correct HTTP verb - even though orval actually knows the right verb.
  3. It forces you to actually pick values for the path parameters, such as getListPetsKey(paramA, paramB). That's inconsistent with the Orval-generated handlers, which just uses a placeholder such as :paramA, :paramB in the URL given to MSW.

Overall, I'm afraid this would not be a good developer experience.

So for now, my team will keep hard-coding URLs in tests. And I would be delighted if in the future, Orval will provide support for more flexible handler overrides.

@soartec-lab
Copy link
Collaborator

Yes, this is an alternative method, but it is consistent and functional with the URL defined in OpenAPI, so please consider it as one method.

const key, _ = getListPetsKey()

@AffeJonsson
Copy link
Contributor

If you want to test different response types, you can use the generateEachHttpStatus config option in output.mock:

output: {
  mock: {
    type: "msw",
    generateEachHttpStatus: true
  }
}

With a specification that have two responses of a ListPets endpoint, one 200 and one 500, the following mock is generated:

export const getListPetsMockHandler200 = (
  overrideResponse?:
    | Pets
    | ((info: Parameters<Parameters<typeof http.get>[1]>[0]) => Pets),
) => {
  return http.get('*/v:version/pets', async (info) => {
    await delay(1000);
    return new HttpResponse(
      JSON.stringify(
        overrideResponse !== undefined
          ? typeof overrideResponse === 'function'
            ? overrideResponse(info)
            : overrideResponse
          : getListPetsResponseMock200(),
      ),
      {
        status: 200,
        headers: {
          'Content-Type': 'application/json',
        },
      },
    );
  });
};

export const getListPetsMockHandler500 = (
  overrideResponse?:
    | Error
    | ((info: Parameters<Parameters<typeof http.get>[1]>[0]) => Error),
) => {
  return http.get('*/v:version/pets', async (info) => {
    await delay(1000);
    return new HttpResponse(
      JSON.stringify(
        overrideResponse !== undefined
          ? typeof overrideResponse === 'function'
            ? overrideResponse(info)
            : overrideResponse
          : getListPetsResponseMock500(),
      ),
      {
        status: 500,
        headers: {
          'Content-Type': 'application/json',
        },
      },
    );
  });
};

And you no longer need to overwrite the whole mock handler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request mock Related to mock generation
Projects
None yet
Development

No branches or pull requests

3 participants