Online Lectures/JavaScript Unit Testing - The Practical

Section8: More on Mocking & Diving Deeper

Nomad Kim 2023. 4. 5. 06:43
  • Mocking Global Objects & Functions
  • Mocking Frontend Features
  • Examples

 

Case1. fetch is a globally available function which is not imported.

Thus, cannot use vi.mock to replace a module in this case. SubGlobal method allows us to replace globally available objects and functions with implementations. Then, Production codes are not effected!

 

sendDataRequest function

export async function sendDataRequest(data) {
  const response = await fetch("https://dummy-site.dev/posts", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(data),
    // body: data,
  });

  const responseData = await response.json();

  if (!response.ok) {
    throw new HttpError(
      response.status,
      "Sending the request failed.",
      responseData
    );
  }

  return responseData;
}

https.test.js

const testResponseData = {
  testKey: "testData",
};
// await fetch('https://dummy-site.dev/posts', { method: 'POST',...in sendDataRequest function
const testFetch = vi.fn((url, options) => {
  return new Promise((resolve, reject) => {
    /**
     * @description test JSON.stringify(required data conversion) works or not
     */
    if (typeof options.body !== "string") return reject("Not a string.");

    const testResponse = {
      //if (!response.ok) { ... in sendDataRequest function
      ok: true,
      //const responseData = await response.json(); ... in sendDataRequest function
      json() {
        return new Promise((resolve, reject) => {
          resolve(testResponseData);
        });
      },
    };
    resolve(testResponse);
  });
});
// SubGlobal method allows us to replace globally available objects 
// and functions with implementations
vi.stubGlobal("fetch", testFetch);

it("should convert the provided data to JSON before sending the request", async () => {
  const testData = { key: "test" };

  let errorMessage;

  try {
    await sendDataRequest(testData);
  } catch (error) {
    errorMessage = error;
  }

  expect(errorMessage).not.toBe("Not a string.");
});

 

Case2. non-ok response. use mockImplementationOnce method

In case of difference on the arguments in response.

it("should throw and httpError in case of non-ok responses", () => {
  testFetch.mockImplementationOnce((url, options) => {
    return new Promise((resolve, reject) => {
      const testResponse = {
        ok: false,
        json() {
          return new Promise((resolve, reject) => {
            resolve(testResponseData);
          });
        },
      };
      resolve(testResponse);
    });
  });

  const testData = { key: "test" };

  return expect(sendDataRequest(testData)).rejects.toBeInstanceOf(HttpError);
});

Case3. local mock values

const testTitle = "Test title";
const testContent = "Test content";
let testFormData;

describe("extractPostData()", () => {
  beforeEach(() => {
    // usage of local mock values!
    testFormData = {
      title: testTitle,
      content: testContent,
      get(identifier) {
        return this[identifier];
      },
    };
  });

  it("should extract title and content from the provided form data", () => {
    const data = extractPostData(testFormData);

    expect(data.title).toBe(testTitle);
    expect(data.content).toBe(testContent);
  });
});