2

For my unit tests I intercept all requests and then responding with mocked date for specific endpoints. I have a pageMockedRequests function that will switch on the endpoint.

My issue comes when I want to change the response I am sending back depending on how many times it has been called.

    const pageMockedRequests = (request) => {
      switch (request.url()) {
        case ENDPOINTS.A:
          return request.respond(jsonResponseWrapper(returnValidToken(), 200));
        case ENDPOINTS.B:
          if (count === 0) {
            return request.respond(jsonResponseWrapper({RESPONSE FOR 1st CALL}, 200));
          } else if (count === 1) {
            return request.respond(jsonResponseWrapper({RESPONSE FOR 2nd CALL}, 200));
          }
        default:
          return request.abort();
      }
    };

The above shows that ENDPOINT.B for the second time will be different from the first response. I'm currently changing it on count.

Is anyone else doing this differently?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
christoffee
  • 633
  • 1
  • 6
  • 15

3 Answers3

0

Using a count seems okay for this implementation. How many different possibilities are there? If you only have two options, simplify it:

let isFirstCall = true;

const pageMockedRequests = (request) => {
  switch (request.url()) {
    case ENDPOINTS.A:
      return request.respond(jsonResponseWrapper(returnValidToken(), 200));
    case ENDPOINTS.B:
      if (isFirstCall) {
        isFirstCall = false;
        return request.respond(jsonResponseWrapper({/* RESPONSE FOR 1st CALL */}, 200));
      }

      return request.respond(jsonResponseWrapper({/* RESPONSE FOR 2nd CALL */}, 200));

    default:
      return request.abort();
  }
};
Matt Shirley
  • 4,216
  • 1
  • 15
  • 19
0

You can use Mockiavelli request mocking library for Puppeteer. Its One-time mocks feature (https://github.com/hltech/mockiavelli#one-time-mocks) allows to define different responses for 1st and 2nd call.

lukaszfiszer
  • 2,591
  • 1
  • 19
  • 13
  • I notice you've posted this same recommendation for a library you've contributed to on a few different answers [here](https://stackoverflow.com/a/61736721/6243352) and [here](https://stackoverflow.com/a/61737073/6243352). See [this meta thread](https://meta.stackoverflow.com/questions/298734/is-it-acceptable-to-promote-my-own-library-as-part-of-a-real-answer) which explains that the answer needs to actually answer the question and disclose your affiliation, neither of which was done on these posts. – ggorlen Nov 24 '20 at 08:28
0

Enumerating every case with an if or switch is fine when there's only a couple of possibilities. But when you wind up with more than 3 or 4 options, the cyclomatic complexity increases beyond maintainability.

A scalable approach uses a data structure that is basically a lookup table for all of your request parameters. One approach is to use a nested object keyed by URL. Each URL points to an inner object keyed by method which keys to an array of responses. For each response array, you could keep an index counter that steps forward whenever responses are sent, or mutate the array by popping or shifting.

Here's a sketch of this approach:

const responsesByEndpoint = {
  "https://www.example.com/api/users": {
    GET: [/* mocked responses */],
    POST: [/* mocked responses */],
    PUT: [/* mocked responses */],
    DELETE: [/* mocked responses */],
  },
  "https://www.example.com/api/posts": {
    GET: [/* mocked responses */],
    POST: [/* mocked responses */],
  },
  "https://www.example.com/api/login": {
    POST: [/* mocked responses */],
  },
  // ...
};

page.on("request", request => {
  try {
    const response = responsesByEndpoint
      [request.url()][request.method()].shift()
    ;
    
    if (response === undefined) {
      throw Error;
    }

    request.respond(response);
  }
  catch (err) {
    request.abort(); // or: request.continue();
  }
});

The data structure of responses by endpoint and method is regular and can be serialized as an external file, although you may need to add body: async () => ({"your": "body"}) functions per response before use during deserialization.

This approach also nicely handles bad requests without changing any code or adding to your switch or if-else chain.

If you need to reset your mocks occasionally, use your test runner's beforeEach block or write a function that returns it fresh.

Even if you're not using this exact mocking approach, this pattern is generic and should save you from if/switch sadness.

ggorlen
  • 44,755
  • 7
  • 76
  • 106