m-radzikowski / aws-sdk-client-mock

AWS JavaScript SDK v3 mocks for easy unit testing. šŸ–‹ļø Typed šŸ”¬ Tested šŸ“„ Documented šŸ› ļø Maintained
https://m-radzikowski.github.io/aws-sdk-client-mock/
MIT License
749 stars 37 forks source link

calls() does not return calls made from exception handler #215

Closed chafey closed 4 weeks ago

chafey commented 3 months ago

Checklist

Bug description

I am writing an integration test that uses two mocked clients. The call to the first mocked client rejects and the second mocked client is called from the catch handler for the call to the first mocked client. calls() on the second mocked client returns an empty array even though the second mocked client was indeed called. I am expecting calls() to return a non empty array for the second mocked client.

Reproduction

import { mockClient } from "aws-sdk-client-mock";
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
import {
  PutMetricDataCommand,
  CloudWatchClient,
} from "@aws-sdk/client-cloudwatch";

const s3Client = new S3Client();
const s3ClientMock = mockClient(s3Client);
s3ClientMock.rejects("error");

const cloudWatchClient = new CloudWatchClient();
const cloudWatchClientMock = mockClient(cloudWatchClient);
cloudWatchClientMock.resolves({});

const input = {
  Namespace: "MyNamespace",
  MetricData: [
    {
      MetricName: "MyMetricName",
    },
  ],
};

describe("multiple mocked clients with one that throws", () => {
  test("calls() for both clients should return 1", async () => {
    s3ClientMock.reset();
    cloudWatchClientMock.reset();
    try {
      try {
        await s3Client.send(
          new GetObjectCommand({ Bucket: "foo", Key: "bar" })
        );
      } catch (e) {
        await cloudWatchClient.send(new PutMetricDataCommand(input));
        throw e;
      }
    } catch (e: any) {}

    expect(s3ClientMock.calls().length).toBe(1);
    expect(cloudWatchClientMock.calls().length).toBe(1); // fails, received is 0
  });
});

Environment

m-radzikowski commented 3 months ago

At the beginning of your test, you reset() both mocks. This resets them to the default behavior - returning undefined for any send() call. As a result, the first call to S3 Client succeeds and does not throw.

It's best practice to reset mocks before every test if you test for different behaviors. But then you need to specify behaviors after the reset. For example:

import {mockClient} from "aws-sdk-client-mock";
import {GetObjectCommand, S3Client} from "@aws-sdk/client-s3";
import {CloudWatchClient, PutMetricDataCommand} from "@aws-sdk/client-cloudwatch";

const s3Client = new S3Client();
const s3ClientMock = mockClient(s3Client);

const cloudWatchClient = new CloudWatchClient();
const cloudWatchClientMock = mockClient(cloudWatchClient);

const input = {
  Namespace: "MyNamespace",
  MetricData: [
    {
      MetricName: "MyMetricName",
    },
  ],
};

beforeEach(() => {
  s3ClientMock.reset();
  cloudWatchClientMock.reset();
})

describe("multiple mocked clients with one that throws", () => {
  test("calls() for both clients should return 1", async () => {
    s3ClientMock.rejects("error");
    cloudWatchClientMock.resolves({});

    try {
      try {
        await s3Client.send(
          new GetObjectCommand({Bucket: "foo", Key: "bar"}),
        );
        console.log("s3Client.send success");
      } catch (e) {
        await cloudWatchClient.send(new PutMetricDataCommand(input));
        throw e;
      }
    } catch (e: any) {
    }

    expect(s3ClientMock.calls().length).toBe(1);
    expect(cloudWatchClientMock.calls().length).toBe(1); // fails, received is 0
  });
});