lista de etiquetas

Promises - Ejemplo de creación y uso, más testing.

Aquí veremos un ejemplo de creación y uso de promises en javascript, y luego cómo testearlo.

Cuando utilizamos fetch esta solo devuelve una promesa como rechazada si hay un error de red, pero si la respuesta del servidor es un status code 404 o 500 fetch no lo considera un error de red y la promesa se resuelve correctamente con esos status en la propiedad response.status y con response.ok como false. Por el contrario, si response.status tiene valores entre 200 y 299, response.ok será true.

Pemos utilizar promises para crear una función customFetch que devuelva una promesa rechazada si el status code de la respuesta no está entre 200 y 299 del siguiente modo:

function customFetch(url) {
  return new Promise(async (resolve, reject) => {
    let fetchPromise = fetch(url);
    fetchPromise
      .then((response) => {
        if (response.ok) {
          resolve(response);
        } else {
          reject(
            new Error(
              `Unexpected status code: ${response.status} ${response.statusText}`
            )
          );
        }
      })
      .catch((error) => {
        reject(error);
      });
  });
}

Vamos a tener también una función delay que utiliza una promise para generar un retraso en la ejecución y poder visualizar los pasos en la consola.

function delay(time = 1000) {
  return new Promise((resolve) => {
    console.log("Delaying...");
    setTimeout(() => {
      resolve();
    }, time || 1000);
  });
}

Y a continuación el código para probar nuestra función customFetch:

console.log("*** Loading...");
await delay();

//con error
let promise = customFetch("https://southparkquotes.onrender.com/v1x/quotes");

//sin error
//let promise = customFetch("https://southparkquotes.onrender.com/v1/quotes");

promise
  .then((response) => {
    console.log("Response: ", response);
  })
  .finally(() => {
    console.log("*** Loaded.");
  })
  .catch((error) => {
    console.error("Catched Error : ", error.message);
  });

Se puede probar cambiar la url de la API para ver cómo se comporta la promesa en cada caso (con y sin error).

Para una explicación detallada de cómo funcionan las promises, recomiendo el libro: Understanding JavaScript Promises de Nicholas C. Zakas

Testing de customFetch

Para hacer el testing de la función customFetch utilicé vitest junto con la librería mock service worker que permite simular el servidor capturando las llamadas a fetch.

Para utilizar mock service worker necesitamos tener un archiv con los handlers de las rutas que vamos a simular, en este caso handlers.js:

import { http, HttpResponse } from "msw";

export const handlers = [
  http.get("https://ok.com", () => {
    return HttpResponse.json({
      rta: "todo ok",
    });
  }),
  http.get("https://devolverErrorDeRed.com", () => {
    return HttpResponse.error();
  }),

  http.get("https://devolver401.com", () => {
    return new HttpResponse(null, { status: 401 });
  }),
];

Y la configuración del servidor en mockserver.js:

import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const mockServer = setupServer(...handlers);

Luego nuestro test queda del siguiente modo:

import { expect, test, beforeAll, afterAll, afterEach } from "vitest";
import { customFetch } from "./customFetch"; // acá va la ruta de tu archivo customFetch.js
import { mockServer } from "./mockserver.js";

beforeAll(() => mockServer.listen());
afterEach(() => mockServer.resetHandlers());
afterAll(() => mockServer.close());

test("customFetch arroja Error cuando la respuesta de fetch es un código que no es 200-299", async () => {
  let rtaError = "";
  try {
    const rta = await customFetch("https://devolver401.com");
  } catch (error) {
    rtaError = error.message;
  }
  expect(rtaError).toBe("Error inesperado: 401 Unauthorized");
});

test("customFetch arroja Error cuando la respuesta de fetch es error de red", async () => {
  let rta = "";
  try {
    rta = await customFetch("https://devolverErrorDeRed.com");
  } catch (error) {
    rta = error;
  }
  expect(rta).toBe("Error de red: Failed to fetch");
});

test("customFetch envía correctamente la respuesta", async () => {
  let rta = await customFetch("https://ok.com");
  let rtaOk = await rta.json();
  expect(rtaOk.rta).toBe("todo ok");
});