fluffy-lionz
fluffy-lionz

Reputation: 81

mock service worker, sad path test failure

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 
}

Upvotes: 2

Views: 3655

Answers (2)

Som Shekhar Mukherjee
Som Shekhar Mukherjee

Reputation: 8188

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

Upvotes: 3

fluffy-lionz
fluffy-lionz

Reputation: 81

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 });
});

Upvotes: 0

Related Questions