One-time implementation with Jest's mockResolvedValueOnce within test remains from one test to another

23 views Asked by At

I'm writing some tests for an AWS lambda that handles and routes notifications from a SNS topic, and if a specific feature flag is off, then it returns immediately. However one of the test's implementation carries over to the next test, and it makes the handler fails when it should not.

MWE for the handler:

export async handler(event) {
  for (record of event.Records) {
    try {
      const {
        applicationId
      } = JSON.parse(JSON.parse(record.body).Message);

      const shouldHandleNotification = await isFeatureFlagOn(PARAMETER_STORE_ARN, FEATURE_FLAG);
      if (!shouldHandleNotification) {
        console.info(`Flag [SHOULD_HANDLE_NOTIFICATION] is off, skipping notification handling.`);
        return;
      }

      const applicationData = await getApplicationData(applicationId);
      if (applicationData.type == "human") {
        // handle application
      } else if (application.type == "viltrumite") {
        // handle application
      } else {
        throw new Error(`Cannot handle application type [${applicationData.type}]`);
      }
    } catch (e) {
      console.error(`Cannot handle record. Error message: ${e.message}`)
    }
  }
}

MWE for the tests:

const mockIsFeatureFlagOn = jest.fn();
jest.mock("moduleIsFeatureFlagOn", () => ({
  isFeatureFlagOn: mockIsFeatureFlagOn,
}));

const mockGetApplicationData = jest.fn();
jest.mock("moduleGetApplicationData", () => ({
  getApplicationData: mockGetApplicationData,
}));

describe("SNSNotificationHandler", () => {
    beforeEach(() => {}
      jest.clearAllMocks();
    });

  // TEST 1
  it("does not handle notification when feature flag is off", async () => {
    mockIsFeatureFlagOn.mockResolvedValueOnce(false);
    mockGetApplicationData.mockResolvedValueOnce({
      type: "unknown"
    });

    await handler(EVENT_WITH_APPLICATION_DATA);

    mockIsFeatureFlagOn.toHaveBeenCalledWith(PARAMETER_STORE_ARN, FEATURE_FLAG);
    mockGetApplicationData.not.toHaveBeenCalled();
  });

  // TEST 2
  it("handles viltrumite application", async () => {
    mockIsFeatureFlagOn.mockResolvedValueOnce(true);
    mockGetApplicationData.mockResolvedValueOnce({
      type: "viltrumite"
    });

    await handler(EVENT_WITH_APPLICATION_DATA);

    mockIsFeatureFlagOn.toHaveBeenCalledWith(PARAMETER_STORE_ARN, FEATURE_FLAG);
    mockGetApplicationData.toHaveBeenCalled();
  });
}

TEST 2 fails because the mockGetApplicationData mock doesn't clear after TEST 1 is done, and it carries over to TEST 2 returning { type: "unknown" } instead of { type: "viltrumite" }. In TEST 1, mockGetApplicationData is never called so its implementation lingers even with the jest.clearAllMocks(); after each test. I could simply delete the mockGetApplicationData's implementation, and that would solve the issue but I'm trying to understand what's happening here.

Now I understand jest.clearAllMocks does not remove mock implementations by design, however my confusion is why a one-time implementation with mockResolvedValueOnce¹ set in a test (i.e., TEST 1) bleeds into another test (i.e., TEST 2)? Shouldn't mockGetApplicationData simply go back to being jest.fn() even if the one-time implementation was never executed because that implementation is scoped to that one test?


¹ mockFn.mockResolvedValueOnce(value) is a shorthand for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) and from the Jest docs: When the mocked function runs out of implementations defined with .mockImplementationOnce(), it will execute the default implementation set with jest.fn(() => defaultValue) or .mockImplementation(() => defaultValue) if they were called.

0

There are 0 answers