Skip to content

Commit 3ed37a9

Browse files
authored
feat(jest): add support for vitest (#231)
1 parent 72ebb74 commit 3ed37a9

15 files changed

+2447
-519
lines changed

.eslintignore

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
**/test-d/**
44
test-e2e/**
55
**/dist/**
6+
7+
**/vitest.config.ts
8+
**/vitest.serializer.ts

README.md

+21-6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ In action:
2929
- [About AWS SDK v3](#about-aws-sdk-v3)
3030
- [Usage](#usage)
3131
- [Install](#install)
32+
- [Versions compatibility](#versions-compatibility)
3233
- [Import](#import)
3334
- [Mock](#mock)
3435
- [DynamoDB DocumentClient](#dynamodb-documentclient)
@@ -38,14 +39,17 @@ In action:
3839
- [SDK v2-style mocks](#sdk-v2-style-mocks)
3940
- [Inspect](#inspect)
4041
- [Reset and restore](#reset-and-restore)
41-
- [Jest matchers](#jest-matchers)
42+
- [Custom matchers](#custom-matchers)
43+
- [Jest](#jest)
44+
- [Vitest](#vitest)
4245
- [API Reference](#api-reference)
4346
- [AWS Lambda example](#aws-lambda-example)
4447
- [Caveats](#caveats)
4548
- [Mixed @smithy/types versions](#mixed-smithytypes-versions)
4649
- [AwsClientStub and strictFunctionTypes](#awsclientstub-and-strictfunctiontypes)
4750
- [Order of mock behaviors](#order-of-mock-behaviors)
4851
- [Order of type and instance mocks](#order-of-type-and-instance-mocks)
52+
- [Using with Mocha](#using-with-mocha)
4953

5054
## About AWS SDK v3
5155

@@ -363,7 +367,7 @@ s3Mock.on(UploadPartCommand).rejects();
363367
#### S3 GetObjectCommand
364368

365369
AWS SDK wraps the stream in the S3 `GetObjectCommand` result to provide utility methods to parse it.
366-
To mock it, you need to install the [`@smithy/util-stream`](https://www.npmjs.com/package/@smithy/util-stream) package
370+
To mock it, you need to install the [`@smithy/util-stream`](https://www.npmjs.com/package/@smithy/util-stream) package
367371
and call the wrapping function `sdkStreamMixin()` on the stream you provide as the command output:
368372

369373
```ts
@@ -510,7 +514,9 @@ You can also pass custom [Sinon Sandbox](https://sinonjs.org/releases/latest/san
510514
with `mockClient(client, { sandbox: mySandbox })`
511515
to manage all mocks lifecycle at once.
512516

513-
### Jest matchers
517+
### Custom matchers
518+
519+
#### Jest
514520

515521
Custom [Jest](https://jestjs.io/) matchers simplify verification
516522
that the mocked Client was called with given Commands.
@@ -556,10 +562,19 @@ expect(snsMock).toHaveReceivedNthSpecificCommandWith(
556562
);
557563
```
558564

559-
Shorter aliases exist, like `toReceiveCommandTimes()`.
565+
Shorter aliases exist, like `toReceiveCommandTimes()`.
566+
567+
#### Vitest
568+
569+
Use those matchers with [Vitest](https://vitest.dev/):
570+
571+
```ts
572+
import 'aws-sdk-client-mock-jest/vitest';
573+
import { expect } from 'vitest';
560574

561-
To use those matchers with [Vitest](https://vitest.dev/), set `test.globals` to `true` in `vite.config.js`
562-
(see [#139](https://github.com/m-radzikowski/aws-sdk-client-mock/issues/139)).
575+
// a PublishCommand was sent to SNS
576+
expect(snsMock).toHaveReceivedCommand(PublishCommand);
577+
```
563578

564579
To use the matchers outside of Jest, you can pull in the [expect](https://www.npmjs.com/package/expect) library separately
565580
and add it to the global scope directly, e.g.:

packages/aws-sdk-client-mock-jest/package.json

+41-6
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,46 @@
2525
"jest-matchers"
2626
],
2727
"scripts": {
28-
"test": "jest --coverage --colors",
28+
"test": "pnpm run jest && pnpm run vitest",
29+
"jest": "jest --coverage --colors ",
30+
"vitest": "vitest run",
2931
"test-types": "tsd",
3032
"build:cjs": "tsc -p tsconfig.json",
3133
"build:es": "tsc -p tsconfig.es.json",
3234
"prebuild": "rimraf dist/",
3335
"build": "pnpm run build:cjs && pnpm run build:es",
3436
"local-publish": "pnpm publish --registry http://localhost:4873/ --no-git-checks"
3537
},
36-
"module": "dist/es/index.js",
37-
"main": "dist/cjs/index.js",
38-
"types": "dist/types/index.d.ts",
38+
"module": "dist/es/jest.js",
39+
"main": "dist/cjs/jest.js",
40+
"types": "dist/types/jest.d.ts",
41+
"exports": {
42+
".": {
43+
"require": {
44+
"types": "./dist/types/jest.d.ts",
45+
"default": "./dist/cjs/jest.js"
46+
},
47+
"import": {
48+
"types": "./dist/types/jest.d.ts",
49+
"default": "./dist/es/jest.js"
50+
}
51+
},
52+
"./vitest": {
53+
"require": {
54+
"types": "./dist/types/vitest.d.ts",
55+
"default": "./dist/cjs/vitest.js"
56+
},
57+
"import": {
58+
"types": "./dist/types/vitest.d.ts",
59+
"default": "./dist/es/vitest.js"
60+
}
61+
}
62+
},
3963
"files": [
4064
"dist"
4165
],
4266
"dependencies": {
67+
"@vitest/expect": ">1.6.0",
4368
"expect": ">28.1.3",
4469
"tslib": "^2.1.0"
4570
},
@@ -49,12 +74,22 @@
4974
"@smithy/types": "1.1.0",
5075
"@types/jest": "29.5.12",
5176
"@types/sinon": "^17.0.3",
77+
"@vitest/coverage-v8": "^2.1.1",
5278
"aws-sdk-client-mock": "workspace:*",
79+
"chalk": "^5.3.0",
5380
"expect": "29.7.0",
54-
"jest-serializer-ansi-escapes": "3.0.0"
81+
"jest-serializer-ansi-escapes": "3.0.0",
82+
"pretty-ansi": "^2.0.0",
83+
"vitest": "^2.1.1"
5584
},
5685
"peerDependencies": {
57-
"aws-sdk-client-mock": "workspace:*"
86+
"aws-sdk-client-mock": "workspace:*",
87+
"vitest": ">1.6.0"
88+
},
89+
"peerDependenciesMeta": {
90+
"vitest": {
91+
"optional": true
92+
}
5893
},
5994
"jest": {
6095
"preset": "ts-jest",

packages/aws-sdk-client-mock-jest/src/index.ts

-1
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/* eslint-disable @typescript-eslint/no-empty-interface */
2+
import type { MatcherContext } from 'expect';
3+
import { expect } from 'expect';
4+
import type { AwsSdkMockMatchers } from './jestMatchers';
5+
import { createBaseMatchers } from './jestMatchers';
6+
import type {
7+
AnySpyCall,
8+
AwsSdkMockAliasMatchers,
9+
CommonMatcherUtils,
10+
MatcherFunction,
11+
} from './types';
12+
13+
/**
14+
* Prettyprints command calls for message
15+
*/
16+
function addCalls(
17+
ctxUtils: CommonMatcherUtils,
18+
calls: AnySpyCall[],
19+
...msgs: string[]
20+
) {
21+
if (calls.length === 0) return msgs.join('\n');
22+
23+
return [
24+
...msgs,
25+
'',
26+
'Calls:',
27+
...calls.map(
28+
(c, i) =>
29+
` ${i + 1}. ${c.args[0].constructor.name}: ${ctxUtils.printReceived(
30+
c.args[0].input
31+
)}`
32+
),
33+
].join('\n');
34+
}
35+
36+
const baseMatchers = createBaseMatchers<MatcherContext['utils']>({
37+
toHaveReceivedCommand: ({
38+
client,
39+
cmd,
40+
notPrefix,
41+
calls,
42+
commandCalls,
43+
ctxUtils,
44+
}) =>
45+
addCalls(
46+
ctxUtils,
47+
calls,
48+
`Expected ${client} to ${notPrefix}receive ${ctxUtils.printExpected(cmd)}`,
49+
`${client} received ${ctxUtils.printExpected(cmd)} ${ctxUtils.printReceived(commandCalls.length)} times`
50+
),
51+
toHaveReceivedCommandTimes:
52+
(expectedCalls) =>
53+
({ calls, client, cmd, commandCalls, notPrefix, ctxUtils }) =>
54+
addCalls(
55+
ctxUtils,
56+
calls,
57+
`Expected ${client} to ${notPrefix}receive ${ctxUtils.printExpected(cmd)} ${ctxUtils.printExpected(expectedCalls)} times`,
58+
`${client} received ${ctxUtils.printExpected(cmd)} ${ctxUtils.printReceived(commandCalls.length)} times`
59+
),
60+
61+
toHaveReceivedCommandWith:
62+
(input) =>
63+
({ client, cmd, notPrefix, data, calls, ctxUtils }) =>
64+
addCalls(
65+
ctxUtils,
66+
calls,
67+
`Expected ${client} to ${notPrefix}receive ${ctxUtils.printExpected(cmd)} with ${ctxUtils.printExpected(input)}`,
68+
`${client} received matching ${ctxUtils.printExpected(cmd)} ${ctxUtils.printReceived(data.matchCount)} times`
69+
),
70+
71+
toHaveReceivedNthCommandWith:
72+
(call, input) =>
73+
({ cmd, client, data, notPrefix, ctxUtils, calls }) =>
74+
addCalls(
75+
ctxUtils,
76+
calls,
77+
`Expected ${client} to ${notPrefix}receive ${call}. ${ctxUtils.printExpected(cmd)} with ${ctxUtils.printExpected(input)}`,
78+
...(data.received
79+
? [
80+
`${client} received ${ctxUtils.printReceived(data.received.constructor.name)} with input:`,
81+
ctxUtils.printDiffOrStringify(input, data.received.input, 'Expected', 'Received', false),
82+
]
83+
: [])
84+
),
85+
toHaveReceivedNthSpecificCommandWith:
86+
(call, input) =>
87+
({ cmd, client, data, notPrefix, ctxUtils, calls }) =>
88+
addCalls(
89+
ctxUtils,
90+
calls,
91+
`Expected ${client} to ${notPrefix}receive ${call}. ${ctxUtils.printExpected(cmd)} with ${ctxUtils.printExpected(input)}`,
92+
...(data.received
93+
? [
94+
`${client} received ${ctxUtils.printReceived(data.received.constructor.name)} with input:`,
95+
ctxUtils.printDiffOrStringify(input, data.received.input, 'Expected', 'Received', false),
96+
]
97+
: [])
98+
),
99+
toHaveReceivedAnyCommand: ({ client, notPrefix, calls, ctxUtils }) =>
100+
addCalls(
101+
ctxUtils,
102+
calls,
103+
`Expected ${client} to ${notPrefix}receive any command`,
104+
`${client} received any command ${ctxUtils.printReceived(calls.length)} times`
105+
),
106+
},
107+
(sample: Record<string, unknown>) => expect.objectContaining(sample)
108+
);
109+
110+
/* typing ensures keys matching */
111+
const aliasMatchers: {
112+
[P in keyof AwsSdkMockAliasMatchers<unknown>]: MatcherFunction<MatcherContext['utils']>;
113+
} = {
114+
toReceiveCommandTimes: baseMatchers.toHaveReceivedCommandTimes,
115+
toReceiveCommand: baseMatchers.toHaveReceivedCommand,
116+
toReceiveCommandWith: baseMatchers.toHaveReceivedCommandWith,
117+
toReceiveNthCommandWith: baseMatchers.toHaveReceivedNthCommandWith,
118+
toReceiveNthSpecificCommandWith:baseMatchers.toHaveReceivedNthSpecificCommandWith,
119+
toReceiveAnyCommand: baseMatchers.toHaveReceivedAnyCommand,
120+
};
121+
122+
// Skip registration if jest expect does not exist
123+
if (typeof expect !== 'undefined' && typeof expect.extend === 'function') {
124+
expect.extend({ ...baseMatchers, ...aliasMatchers });
125+
}
126+
127+
/**
128+
* Types for @types/jest
129+
*/
130+
declare global {
131+
namespace jest {
132+
interface Matchers<R = void> extends AwsSdkMockMatchers<R> {}
133+
}
134+
}
135+
declare module 'expect' {
136+
interface Matchers<R = void> extends AwsSdkMockMatchers<R> {}
137+
}

0 commit comments

Comments
 (0)