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

Mocked ECSClient (using mockClient(ECSClient)) is not assignable to parameter of type 'ECSClient' #223

Open thevictorchang opened 1 month ago

thevictorchang commented 1 month ago

Checklist

Bug description

I did read the Caveats docs and don't think this is quite the issue that is being described there - correct me if I am wrong though.

I have a class that takes an ECSClient object (imported from @aws-sdk/client-ecs) as a parameter in it's constructor but trying to pass a mocked one in gives the error:

Argument of type 'AwsStub<ServiceInputTypes, ServiceOutputTypes, ECSClientResolvedConfig>' is not assignable to parameter of type 'ECSClient'.
  Type 'AwsStub<ServiceInputTypes, ServiceOutputTypes, ECSClientResolvedConfig>' is missing the following properties from type 'ECSClient': config, destroy, getDefaultHttpAuthSchemeParametersProvider, getIdentityProviderConfigProvider, middlewareStackts(2345)

It seems that the type of the mocked ECSClient isn't quite what is expected, or perhaps I am misunderstanding the goal of the library?

Reproduction

Class under test:


import {
  ECSClient,

} from '@aws-sdk/client-ecs';

export class EcsDeploymentMonitor implements IDeploymentMonitor {
  ...

  constructor(private stage: StageMetadata, private service: ServiceMetadata, private ecsClient: ECSClient,
    timeoutMinutes?: number, tag?: string) {
    ...
  }

Test file:

import {
  ECSClient,
} from '@aws-sdk/client-ecs';
import { describe, it, jest } from '@jest/globals';
import { mockClient } from 'aws-sdk-client-mock';
import 'aws-sdk-client-mock-jest';
import { EcsDeploymentMonitor } from '../../src/utils/ecs-deployment-monitor';

const ecsMock = mockClient(ECSClient);

describe('#someMethod', () => {
  it('is implemented', async () => {
    const ecsDeploymentMonitor = new EcsDeploymentMonitor(stage, service, ecsMock);
    await ecsDeploymentMonitor.getRunningTaskArnIds();
  });
});

package.json:

...
  "dependencies": {
    "@aws-cdk/cloud-assembly-schema": "2.136.1",
    "@aws-cdk/cloudformation-diff": "2.136.1",
    "@aws-cdk/cx-api": "2.136.1",
    "@aws-sdk/client-ecs": "^3.109.0",
    "@aws-sdk/util-arn-parser": "^3.55.0",
    "@envato/cdk-deploy-with-change-set-review-plugin": "0.23.0",
    "@oclif/core": "^3",
    "@oclif/plugin-help": "^6",
    "aws-cdk": "2.136.1",
    "aws-cdk-lib": "2.136.1",
    "aws-sdk": "^2.1586.0",
    "chalk": "^4",
    "change-case": "^4.1.2",
    "constructs": "^10.1.38",
    "inquirer": "^8.2.4",
    "json-diff": "^0.9.0",
    "minimatch": "^9.0.3",
    "mustache": "^4.2.0",
    "oclif": "^4.4.5",
    "param-case": "^3.0.4",
    "promptly": "^3.2.0",
    "table": "^6.8.0",
    "ts-node": "^10.8.1",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@aws-sdk/client-ecs": "^3.109.0",
    "@jest/globals": "^29.6.1",
    "@oclif/test": "^3",
    "@octokit/core": "^3.6.0",
    "@octokit/plugin-rest-endpoint-methods": "^5.13.0",
    "@types/inquirer": "^8.2.1",
    "@types/json-diff": "^0.7.0",
    "@types/mustache": "^4.1.3",
    "@types/node": "^20",
    "@types/promptly": "^3.0.2",
    "@types/semver": "^7.3.9",
    "@types/uuid": "^8.3.4",
    "@typescript-eslint/eslint-plugin": "^6.2.0",
    "@typescript-eslint/parser": "^6.2.0",
    "aws-sdk-client-mock": "^4.0.0",
    "aws-sdk-client-mock-jest": "^4.0.0",
    "eslint": "^8.45.0",
    "eslint-import-resolver-node": "^0.3.7",
    "eslint-import-resolver-typescript": "^3.5.5",
    "eslint-plugin-import": "^2.27.5",
    "jest": "^29.6.1",
    "semver": "^7.3.7",
    "ts-jest": "^29.1.1",
    "typescript": "^5.1.6"
  },
  "peerDependencies": {
    "@aws-cdk/cloud-assembly-schema": "2.136.1",
    "@aws-cdk/cloudformation-diff": "2.136.1",
    "@aws-cdk/cx-api": "2.136.1",
    "aws-cdk": "2.136.1",
    "aws-cdk-lib": "2.136.1",
    "aws-sdk": "^2.1586.0",
    "constructs": "^10.1.38",
    "minimatch": "^9.0.3"
  },
...

Environment

tmeisenh commented 1 month ago

I'm getting a similar error with a class's constructor not able to take the mocked client but I'm trying to mock the SNSClient.
I actually get a type error if I fully type the output of mockClient

const thing: AwsClientStub<SNSClient> = mockClient(SNSClient);
Type 'AwsStub<ServiceInputTypes, ServiceOutputTypes, SmithyResolvedConfiguration<HttpHandlerOptions>>' is not assignable to type 'AwsStub<ServiceInputTypes, ServiceOutputTypes, SNSClientResolvedConfig>'.
  Type 'SmithyResolvedConfiguration<HttpHandlerOptions>' is not assignable to type 'SNSClientResolvedConfig'.
    Type 'SmithyResolvedConfiguration<HttpHandlerOptions>' is missing the following properties from type 'Required<ClientDefaults>': sha256, urlParser, bodyLengthChecker, streamCollector, and 17 more.ts(2322)

That gives me a Smithy-types error that the caveats section was unable to resolve.

Crafoord commented 1 month ago

This seems to be broken in the latest STSClient currently at version (3.575.0): https://www.npmjs.com/package/@aws-sdk/client-sts/v/3.575.0

It works in the previous version (3.574.0): https://www.npmjs.com/package/@aws-sdk/client-sts/v/3.574.0

Seems like the 3.575.0 release includes som breaking changes regarding smithy https://github.com/aws/aws-sdk-js-v3/releases/tag/v3.575.0

In my STSClient case: https://github.com/aws/aws-sdk-js-v3/commit/6877db975b7676ae9535cc4a072828a855de8f5f#diff-c00e767676da286301fe37f850b4c4cdaa0bf86930bcb97a454342605f95938e

throrin19 commented 1 month ago

Same problem here with SNSClient or others AWS clients

EDIT : They migrate some parts to Smithy packages : https://github.com/aws/aws-sdk-js-v3/issues/5102

Maybe this package needs upgrade to add compatibility with this part.

Crafoord commented 1 month ago

Looks like there have been similar issues before: https://github.com/m-radzikowski/aws-sdk-client-mock/issues/197

I wonder if this is a new problem or the same? Although the major change in smithy smells bad 👃

thevictorchang commented 1 month ago

We've found a workaround:

const ecsClientMock = mockClient(ECSClient);
const ecsClient = (ecsClientMock as unknown) as ECSClient;

We can mock the result of commands with ecsClientMock.on(ListTaskscommand).resolves({ ... }) but pass in the ecsClient where it's needed. Not sure if this is the intended workaround...

kornicameister commented 1 month ago

Fixed by: https://github.com/m-radzikowski/aws-sdk-client-mock/issues/197


I cannot even create mock with sesv2@3.576.0

error when trying to create a mock:

test/verifier.test.ts:15:26 - error TS2345: Argument of type 'typeof SESv2Client' is not assignable to parameter of type 'InstanceOrClassType<Client<ServiceInputTypes, MetadataBearer, SmithyResolvedConfiguration<HttpHandlerOptions>>>'.

15     const sesStub = mock(SESv2Client);
                            ~~~~~~~~~~~

test/verifier.test.ts:30:13 - error TS2345: Argument of type 'typeof GetEmailIdentityCommand' is not assignable to parameter of type 'new (input: GetEmailIdentityCommandInput) => AwsCommand<GetEmailIdentityCommandInput, MetadataBearer, any, any>'.
  Construct signature return types 'GetEmailIdentityCommand' and 'AwsCommand<GetEmailIdentityCommandInput, MetadataBearer, any, any>' are incompatible.
    The types of 'middlewareStack.concat' are incompatible between these types.
      Type '<InputType extends GetEmailIdentityCommandInput, OutputType extends GetEmailIdentityCommandOutput>(from: MiddlewareStack<InputType, OutputType>) => MiddlewareStack<...>' is not assignable to type '<InputType extends GetEmailIdentityCommandInput, OutputType extends MetadataBearer>(from: MiddlewareStack<InputType, OutputType>) => MiddlewareStack<...>'.
        Types of parameters 'from' and 'from' are incompatible.
          Type 'MiddlewareStack<InputType, OutputType>' is not assignable to type 'MiddlewareStack<InputType, GetEmailIdentityCommandOutput>'.
            Types of property 'addRelativeTo' are incompatible.
              Type '(middleware: MiddlewareType<InputType, OutputType>, options: RelativeMiddlewareOptions) => void' is not assignable to type '(middleware: MiddlewareType<InputType, GetEmailIdentityCommandOutput>, options: RelativeMiddlewareOptions) => void'.
                Types of parameters 'middleware' and 'middleware' are incompatible.
                  Type 'MiddlewareType<InputType, GetEmailIdentityCommandOutput>' is not assignable to type 'MiddlewareType<InputType, OutputType>'.
                    Type 'InitializeMiddleware<InputType, GetEmailIdentityCommandOutput>' is not assignable to type 'MiddlewareType<InputType, OutputType>'.
                      Type 'InitializeMiddleware<InputType, GetEmailIdentityCommandOutput>' is not assignable to type 'InitializeMiddleware<InputType, OutputType>'.
                        Call signature return types 'InitializeHandler<InputType, GetEmailIdentityCommandOutput>' and 'InitializeHandler<InputType, OutputType>' are incompatible.
                          Type 'Promise<InitializeHandlerOutput<GetEmailIdentityCommandOutput>>' is not assignable to type 'Promise<InitializeHandlerOutput<OutputType>>'.
                            Type 'InitializeHandlerOutput<GetEmailIdentityCommandOutput>' is not assignable to type 'InitializeHandlerOutput<OutputType>'.
                              Types of property 'output' are incompatible.
                                Type 'GetEmailIdentityCommandOutput' is not assignable to type 'OutputType'.
                                  'GetEmailIdentityCommandOutput' is assignable to the constraint of type 'OutputType', but 'OutputType' could be instantiated with a different subtype of constraint 'MetadataBearer'.

and I cannot call expect

test/verifier.test.ts:43:45 - error TS2345: Argument of type 'typeof GetEmailIdentityCommand' is not assignable to parameter of type 'new (input: GetEmailIdentityCommandInput) => AwsCommand<GetEmailIdentityCommandInput, MetadataBearer>'.
  Type 'GetEmailIdentityCommand' is not assignable to type 'AwsCommand<GetEmailIdentityCommandInput, MetadataBearer>'.

43       expect(sesStub).toHaveReceivedCommand(GetEmailIdentityCommand);
evanstachowiak commented 1 month ago

I'm also having this issue with the EC2Client

throrin19 commented 4 weeks ago

Any news about that ?

m-radzikowski commented 3 weeks ago

With both @aws-sdk/client-ecs v3.575.0 and v3.588.0 (current latest) the mockClient(ECSClient) works fine without type errors. Same for EC2 client.

The package.json:

    "@aws-sdk/client-ecs": "^3.109.0",

means any version between 3.109.0 and 4.0.0. If you have @aws-sdk/* packages in different versions installed, they can install separate types in different versions, leading to this problem.

The solution steps, as @Crafoord noted, are described in #197

If the problem persists, see the last point in the issue on providing the exact dependency versions. Even better if you create a repo with reproduction.

I'll leave it open for a bit and close later if there are no further problems.

evanstachowiak commented 3 weeks ago

@m-radzikowski thanks for the update!

I updated all my aws-sdk dependencies and additionally added the override and it is working for me now.