JIRA Forge: Unit Testing

Hi team,

I am working on some scripts to work with JIRA Forge.

Is there any support for automated unit testing?

Thank you!

1 Like

Hi @DJO – I don’t believe Forge provides any specialized unit testing support or convenience libraries. (Someone should correct me if I’m wrong.) However, you can probably use whatever JS testing framework you prefer.

Personally, I’ve had good success using Jest with Jest Mock Modules for mocking the Forge modules. React Testing Library has been useful for testing a Forge Custom UI. The biggest challenge I’ve personally encountered is unit testing the Forge UI Kit in the same way React Testing Library or Enzyme can be used for testing React UIs. I’m not sure how to go about that.

Not sure if this at all answered your question. Is there something more specific you’re looking for?

1 Like

@AaronMorris1 This helps! Thank you for chiming in.

I just want some code coverage to help us scale the codebase. I will take a look at Jest next.

:beers: cheers!

@AaronMorris1 Do you happen to have a sample repo to see how Jest works?

I have been having a hard time trying to mock @forge/api. I keep getting this error:

    TypeError: Cannot read properties of undefined (reading 'fetch')

Hi @DJO ,
Sorry, I may have put you on a slightly wrong path. In my original response, I referenced Jest Mock Modules, but I also use Jest Manual Mocks. That was the key to faking the Forge modules. I don’t have a repository to share, but I can provide some basic snippets.

This is a high-level example for mocking the @forge/bridge module. Probably you can apply a similar pattern to @forge/api:

  1. Create a __mocks__ folder parallel to your node_modules folder. Inside the __mocks__ folder, simulate a path to the forge module you want to fake. In my case, I created the following file path: __mocks__/@forge/bridge.ts
    image

  2. Inside your mock module, implement a stub of the Forge module you’re faking. You just need to implement enough to support your unit tests. In this example, I faked the requestJira() function of the @forge/bridge module. I also implemented a few utility functions to support my unit tests: getMockRequestJira(), resetMocks(), and setResponse().

// bridge.ts
import {resetAllWhenMocks, when} from "jest-when";

const mockRequestJira = jest.fn();

const getMockRequestJira = (): jest.Mock => {
    return mockRequestJira;
};

const requestJira = async (requestUrl: string, fetchOptions: FetchOptions): Promise<Response> => {
    return mockRequestJira(requestUrl, fetchOptions);
};

const resetMocks = (): void => {
    resetAllWhenMocks();
    mockRequestJira.mockClear();
}

const setResponse = (requestUrl: string, method: string, isSuccessful: boolean, responseContent: string): void => {
    when(mockRequestJira)
        .calledWith(requestUrl, expect.objectContaining({method: method}))
        .mockResolvedValue(new Response(isSuccessful, responseContent));
}

interface FetchOptions {
    method: string;
    body: string | undefined;
}

class Response {
    constructor(isSuccessful: boolean, responseText: string) {
        this.ok = isSuccessful;
        this.responseText = responseText;
    }

    ok: boolean;
    responseText: string;
    calls = 0;
    text(): string {
        return this.responseText;
    }
}

export {getMockRequestJira, requestJira, resetMocks, setResponse};
  1. Use your fake module in a unit test. Use the jest.mock() on the target Forge module to tell Jest to load your fake version instead of the real one. And you can import any utility functions you created as normal. Any production code that uses the Forge module will automatically get the fake version of the module when run from the context of a unit test.

Here’s an example of a utility function that makes GET requests against the Jira API using @forge/bridge along with some example unit tests:

// jira-api-wrapper.ts
import {requestJira} from "@forge/bridge";

class FetchOptions {
    method: string;
    headers = {"Content-Type": "application/json"};
    body: string | undefined;

    constructor(method: string, body: string | undefined = undefined) {
        this.method = method;
        this.body = body;
    }
}

const getAsUser = async (requestUrl: string, description: string): Promise<null|string> => {
    const fetchOptions = new FetchOptions("GET");
    return await sendRequest(requestUrl, fetchOptions, description);
};

export {getAsUser};

async function sendRequest(requestUrl: string, fetchOptions: FetchOptions, description: string): Promise<null|string> {
    console.debug(description, fetchOptions.method, requestUrl);
    const response = await requestJira(requestUrl, fetchOptions);

    if (!response.ok) {
        await logError(requestUrl, response, description);
        return null;
    }

    const results = await response.text();

    return results ? results: "success";
}

async function logError(url: string, response: globalThis.Response, description: string) {
    const message =
        `Error -- Operation: ${description}; Status Code: ${response.status}; URL: ${url}; Details: ${await response.text()}`;
    console.warn(message);
}
// jira-api-wrapper.test.ts

jest.mock("@forge/bridge");
jest.spyOn(global.console, "warn").mockImplementation(() => {});

import {getAsUser} from "../jira-api-wrapper";
import {setResponse, getMockRequestJira, resetMocks} from "../../../__mocks__/@forge/bridge";

beforeEach(() =>{
   resetMocks();
});

describe("getAsUser", () => {
   it("should send a GET rest call to Jira", async () => {
      setResponse("/my/jira/endpoint", "GET", true, "Success")

      await getAsUser("/my/jira/endpoint", "testing getAsUser");

      const mock = getMockRequestJira().mock;
      expect(mock.calls.length).toBe(1);

      const actual = mock.calls[0];
      expect(actual[0]).toBe("/my/jira/endpoint");
      expect(actual[1].method).toBe("GET");
   });

   it("should return Jira's response on success", async () => {
      setResponse("/my/jira/endpoint", "GET", true, "Expected Response")

      const result = await getAsUser("/my/jira/endpoint", "testing getAsUser");
      expect(result).toBe("Expected Response");
   });

   it("should return null on failure", async () => {
      setResponse("/my/jira/endpoint", "GET", false, "Call failed")

      const result = await getAsUser("/my/jira/endpoint", "testing getAsUser");
      expect(result).toBeNull();
   });
});

These code snippets aren’t the greatest, but hopefully they get you on the right path. If you have any specific issues, then please share some code snippets for troubleshooting. Sorry about the confusion from my original response.

-Aaron

2 Likes

This is great. Thank you for sharing, @AaronMorris1

@DJO Hi, I have the same problem at the moment while trying to write tests for UI Kit. Did you ever manage to mock the api functions of @forge/api? And if so, could maybe share a snippet how you did it?

Thanks in advance!

@DanielKleissl did you find a way to mock forge/api?

Sadly, no. It seemed to me from the error messages I got that there seems to be a little more going on behind the scenes with UI Kit, but I did not find what else I could mock or how the inner workings of forge’s UI Kit are put together.

I sadly don’t remember exactly the error, because I decided to give up and did not save it.