Custom matchers for AWS SDK Client mock to be used in vitest
npm install aws-sdk-client-mock-vitest


This module adds custom matchers to verfiy calls to your AWS Client Mock.
It was heavily inspired by aws-sdk-client-mock-jest.
You develop code that makes use of the AWS SDK for JavaScript v3.
You are already writing tests for it through the great aws-sdk-client-mock package.
You also want to ensure that your actual code performs certain calls against your AWS Client Mocks. While there
is aws-sdk-client-mock-jest you prefer
vitest.
You can use this module to use expect extensions for vitest to ensure certain commands have been called
on your AWS clients.
``console`
npm install --save-dev aws-sdk-client-mock-vitest
> [!IMPORTANT]
> This package depends on @vitest/expect so you normally want to use a packagevitest
> version that fits the vite version you are using.
> The latest version should work for version 4. If you are using anvitest
> older version of , you should stick to an earlier version as well.3
>
> If you use vitest :2
>
> npm run install --save-dev aws-sdk-client-mock-vitest@^6.2.1
>
> If you use vitest :1
>
> npm run install --save-dev aws-sdk-client-mock-vitest@^5.1.0
>
> If you use vitest :
>
> npm run install --save-dev aws-sdk-client-mock-vitest@^3.0.0
In order to use the new matchers, we have to register them. The easiest way is
to do this in a setup file.
You can either use a helper to extend with all matchers or manually only extend
the matchers you are interested in.
Create a a new file tests/setup.ts with the following content (or adapt an
existing one):
`javascript`
// tests/setup.ts
import "aws-sdk-client-mock-vitest/extend";
Now ensure this file is loaded by vitest before each test by adapting your
vite.config.ts file:
`javascript
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
// Ensure our file is loaded and matchers are extended
setupFiles: ["tests/setup.ts"],
},
});
`
Alternatively, you can manually register matchers.
Create a a new file tests/setup.ts with the following content (or adapt an
existing one):
`javascript
// tests/setup.ts
import { expect } from "vitest";
import { allCustomMatcher } from "aws-sdk-client-mock-vitest";
expect.extend(allCustomMatcher);
`
We can only extend with individual matchers:
`javascript
// tests/setup.ts
import { expect } from "vitest";
import {
toReceiveCommandTimes,
toHaveReceivedCommandTimes,
toReceiveCommandOnce,
toHaveReceivedCommandOnce,
toReceiveCommand,
toHaveReceivedCommand,
toReceiveCommandWith,
toHaveReceivedCommandWith,
toReceiveNthCommandWith,
toHaveReceivedNthCommandWith,
toReceiveLastCommandWith,
toHaveReceivedLastCommandWith,
toReceiveAnyCommand,
toHaveReceivedAnyCommand,
toReceiveCommandExactlyOnceWith,
toHaveReceivedCommandExactlyOnceWith,
} from "aws-sdk-client-mock-vitest";
expect.extend({
toReceiveCommandTimes,
toHaveReceivedCommandTimes,
toReceiveCommandOnce,
toHaveReceivedCommandOnce,
toReceiveCommand,
toHaveReceivedCommand,
toReceiveCommandWith,
toHaveReceivedCommandWith,
toReceiveNthCommandWith,
toHaveReceivedNthCommandWith,
toReceiveLastCommandWith,
toHaveReceivedLastCommandWith,
toReceiveAnyCommand,
toHaveReceivedAnyCommand,
toReceiveCommandExactlyOnceWith,
toHaveReceivedCommandExactlyOnceWith,
});
`
Now ensure this file is loaded by vitest before each test by adapting your
vite.config.ts file:
`javascript
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
// Ensure our file is loaded and matchers are extended
setupFiles: ["tests/setup.ts"],
},
});
`
> [!NOTE]
> If you're using the automatic setup with
> import "aws-sdk-client-mock-vitest/extend" andnode16
> a moduleResoltion
> setting of at least or bundler TypeScript declarations are
> automatically included and you can skip this section.
Create a vitest.d.ts file with the following content
`javascript
// tests/vitest.d.ts
import "vitest";
import { CustomMatcher } from "aws-sdk-client-mock-vitest";
declare module "vitest" {
interface Matchers
}
`
> [!TIP]
> If you are using eslint in your project you may want to add the followingvitest.d.ts
> lines at the beginning of your file:`
>
> javascript`
> / eslint-disable @typescript-eslint/no-empty-object-type /
> / eslint-disable @typescript-eslint/no-explicit-any /
>
If you get the following error in your tests
``
Error: Invalid Chai property: toHaveReceivedCommandWith
Then you probably forgot to run expect.extend with the matcher you are using in your test (see above)
Lets assume you have written a function to read a secret from the AWS Secrets
Manager. It may look like this:
`typescript
// src/main.ts
import {
SecretsManagerClient,
GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
export async function readSecret(secretId: string): Promise
const client = new SecretsManagerClient({});
const command = new GetSecretValueCommand({ SecretId: secretId });
const response = await client.send(command);
if (response.SecretString) {
return response.SecretString;
}
throw new Error("Unable to read the secret");
}
`
Naturally we want to test this function to verify it either returns the secret
when found or raises an exception otherwise. But we do not want to do actual
AWS API calls. We can write a test with vite without doing any network requestsaws-sdk-client-mock
thanks to . This test may look like this:
`typescript
// tests/main.test.ts
import { describe, it, expect } from "vitest";
import { mockClient } from "aws-sdk-client-mock";
import {
GetSecretValueCommand,
SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";
import { readSecret } from "../src/main";
const smMock = mockClient(SecretsManagerClient);
describe("readSecret", () => {
it("should return the secret value", async () => {
/ Setup our mock. In this test the secret will always be secr3t /
smMock.on(GetSecretValueCommand).resolves({ SecretString: "secr3t" });
const result = await readSecret("foo");
expect(result).toBe("secr3t");
// We have not verified that we actually interacted with our
// Secret Manager correcty
});
});
`
The test above verifies our readSecret function does indeed return the valueSecretString
of the secret when the API response includes a . However we have
not validated we are actually retrieving the correct secret.
We may want to actually inspect our mock client to verify we sent
a specific command. We can do this with our custom expect matcher
expect(mockClient).toHaveReceivedCommandWith(...).
To make use of it we are changing our testfile and registering
custom matchers
`typescript
// tests/main.test.ts
import { describe, it, expect } from "vitest";
import { mockClient } from "aws-sdk-client-mock";
import {
GetSecretValueCommand,
SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";
import {
CustomMatcher,
toHaveReceivedCommandWith,
} from "aws-sdk-client-mock-vitest";
/ you can also run this in setupTests, see above /
expect.extend({ toHaveReceivedCommandWith });
/ You may want to put this in some vitest.d.ts, see above /
declare module "vitest" {
interface Assertion
interface AsymmetricMatchersContaining extends CustomMatcher {}
}
import { readSecret } from "../src/main";
const smMock = mockClient(SecretsManagerClient);
describe("readSecret", () => {
it("should read it", async () => {
smMock.on(GetSecretValueCommand).resolves({ SecretString: "secr3t" });
const result = await readSecret("foo");
expect(result).toBe("secr3t");
/ Ensure we use the inut of the function to fetch the correct secret /
expect(smMock).toHaveReceivedCommandWith(GetSecretValueCommand, {
SecretId: "foo",
});
});
});
`
In order to run tests locally, execute the following
`console`
npm ci
npm run test:coverage
If you get an ERR_INSPECTOR_NOT_AVAILABLE error, make sure your nodejs is compiled withinspector support. Otherwise run npm run test to skip code coverage
I would like to thank Maciej Radzikowski for the awesome aws-sdk-client-mock andaws-sdk-client-mock-jest` packages. These helped a lot testing AWS code and also
helped building this library