2

Recently just using Mock Service Worker to test my HTTP requests, i'm looking to test my failure path.

My first test passes (happy), but the error i'm receiving for my failure is "Unexpected end of JSON input"

It does behave in the way that I would like, but from a testing point of view i'm a little confused.

How can I get my failure path to pass the test?

My test file

import "whatwg-fetch";
import { rest } from "msw";
import { setupServer } from "msw/node";

import { collect } from "./collect";

const server = setupServer(
  rest.get(
    "http://api.openweathermap.org/data/2.5/weather",
    (req, res, ctx) => {
      return res(
        ctx.status(200),
        ctx.json({ base: "stations", clouds: { all: 6 }, cod: 200 })
      );
    }
  )
);

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

it("collects data", async () => {
  const res = await collect();
  expect(res).toEqual({ base: "stations", clouds: { all: 6 }, cod: 200 });
});

it("handles failure", async () => {
  server.use(
    rest.get(
      "http://api.openweathermap.org/data/2.5/weather",
      (req, res, ctx) => {
        return res(ctx.status(401));
      }
    )
  );
  await expect(collect()).rejects.toThrow("401");
});

My fetch async function

require('dotenv').config()

export const collect = async () => {
    const key = process.env.REACT_APP_API_KE
    // try{
      const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=london&appid=${key}`)
      if(res.status !== 200){
        const error = await res.json()
        throw { message: error.message, status: error.cod }
      }
        const data = await res.json()
        return data 
}
fluffy-lionz
  • 81
  • 2
  • 12

2 Answers2

3

Fixing the mock server

The problem is that the collect function is expecting a JSON response even in case of an error, but your mock server doesn't return that. So, when you do res.json() in your collect function you get an error.

Update your response resolver to return a json response.

return res(ctx.json({message: "error"}), ctx.status(401));

Fixing the test

toThrow is not the correct matcher here, because async functions always return promise and in your case the collect function returns a promise that gets rejected with the thrown data.
So, you can use the toEqual matcher instead.

Also you need to update the way the error is tested. You can opt any of the following options:

Using rejects matcher:

it("handles failure", () => {
  server.use(
    rest.get(
      "http://api.openweathermap.org/data/2.5/weather",
      (req, res, ctx) => {
        return res(ctx.json({message: "error"}), ctx.status(401));
      }
    )
  );
  return expect(collect()).rejects.toEqual({ message: "error", status: 401 });
});

Using async/await syntax:

it("handles failure", async () => {
  server.use(
    rest.get(
      "http://api.openweathermap.org/data/2.5/weather",
      (req, res, ctx) => {
        return res(ctx.status(401));
      }
    )
  );
  try {
    await collect();
  } catch (err) {
    expect(err).toEqual({ message: "error", status: 401 });
  }
});

Using .catch

But in this approach you need to explicitly check that your catch assertion has been called, otherwise a fulfilled promise will not fail your test.

it("handles failure", async () => {
  server.use(
    rest.get(
      "http://api.openweathermap.org/data/2.5/weather",
      (req, res, ctx) => {
        return res(ctx.status(401));
      }
    )
  );
  expect.assertions(1);
  return collect().catch((err) =>
    expect(err).toEqual({ message: "error", status: 401 })
  );
});

Fixing the collect function

In your collect function the status should be res.status and not data.code.

And you can also clean the following code a bit by moving the res.json() call out of the conditional.

require("dotenv").config();

export const collect = async () => {
  const key = process.env.REACT_APP_API_KEY;
  const res = await fetch(
    `http://api.openweathermap.org/data/2.5/weather?q=london&appid=${key}`
  );
  const data = await res.json();
  if (res.status !== 200) {
    throw { message: data.message, status: res.status };
  }
  return data;
};

And also you shouldn't be storing secrets in react environment variables, that would be exposed. Docs enter image description here

Som Shekhar Mukherjee
  • 4,701
  • 1
  • 12
  • 28
  • Thank you for your suggestions, unfortunately i'm still getting `Received message: "Unexpected end of JSON input"` Is this because i'm using the error message that has been received from the API, and not an error object? – fluffy-lionz Jan 13 '22 at 17:12
  • I've updated my answer, can you take a look if this resolves your issue @fluffy-lionz? – Som Shekhar Mukherjee Jan 13 '22 at 17:22
  • Also, the problem is not because you're using the error message received from the server, it's completely fine. The problem is that your mock server doesn't resemble your actual server. If the actual server returns an error message then the mock server should also. Hope that makes sense! – Som Shekhar Mukherjee Jan 13 '22 at 17:30
  • Ah ok, i've had a play around with it and my new fail is `Received function did not throw` – fluffy-lionz Jan 13 '22 at 17:41
  • @fluffy-lionz I've added detailed explanation why `toThrow` is not the correct choice here, please have a look – Som Shekhar Mukherjee Jan 13 '22 at 18:18
  • 1
    that was really close, what i had to do was change the value of my status in my throw object to the response status ` if (res.status !== 200) { throw { message: data.message, status: res.status }; } ` tests are now working, thanks for your help – fluffy-lionz Jan 14 '22 at 16:21
0

With some help I updated my collect function so it was the following I'm now sending the value of the response as the status

require("dotenv").config();

export const collect = async () => {
  const key = process.env.REACT_APP_API_KEY;
  const res = await fetch(
    `http://api.openweathermap.org/data/2.5/weather?q=london&appid=${key}`
  );
  const data = await res.json();
  if (res.status !== 200) {
    throw { message: data.message, status: res.status };
  }
  return data;
};

My test now looks like this, I had to use the toEqual matcher instead of toThrow and have it return instead of await / async

it("handles failure", () => {
  server.use(
    rest.get(
      "http://api.openweathermap.org/data/2.5/weather",
      (req, res, ctx) => {
        return res(ctx.json({message: "error"}), ctx.status(401));
      }
    )
  );
  return expect(collect()).rejects.toEqual({ message: "error", status: 401 });
});
fluffy-lionz
  • 81
  • 2
  • 12