Skip to content

Commit 267a926

Browse files
committed
feat: add support for vitest
1 parent a88b565 commit 267a926

File tree

15 files changed

+2470
-520
lines changed

15 files changed

+2470
-520
lines changed

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ s3Mock.on(UploadPartCommand).rejects();
356356
#### S3 GetObjectCommand
357357

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

362362
```ts
@@ -517,7 +517,9 @@ npm install -D aws-sdk-client-mock-jest
517517
Usage (notice the `import`):
518518

519519
```ts
520-
import 'aws-sdk-client-mock-jest';
520+
import 'aws-sdk-client-mock-jest'; // default for jest
521+
import 'aws-sdk-client-mock-jest/jest-globals'; // for @jest/globals
522+
521523

522524
// a PublishCommand was sent to SNS
523525
expect(snsMock).toHaveReceivedCommand(PublishCommand);
@@ -549,10 +551,17 @@ expect(snsMock).toHaveReceivedNthSpecificCommandWith(
549551
);
550552
```
551553

552-
Shorter aliases exist, like `toReceiveCommandTimes()`.
554+
Shorter aliases exist, like `toReceiveCommandTimes()`.
555+
556+
Use those matchers with [Vitest](https://vitest.dev/):
557+
558+
```ts
559+
import 'aws-sdk-client-mock-jest/vitest';
560+
import { expect } from 'vitest';
553561

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

557566
To use the matchers outside of Jest, you can pull in the [expect](https://www.npmjs.com/package/expect) library separately
558567
and add it to the global scope directly, e.g.:
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { appendFileSync } from "fs";
2+
import prettyAnsi from "pretty-ansi";
3+
import type { SnapshotSerializer } from "vitest";
4+
5+
export default {
6+
serialize(text, config, indentation, depth, refs, printer) {
7+
appendFileSync("snapshot.log", text + "\n")
8+
9+
return printer(prettyAnsi(typeof text === 'string' ? text : text.message), config, indentation, depth, refs);
10+
},
11+
test(val) {
12+
return (
13+
(typeof val === "string" && val.includes(`\x1B`)) ||
14+
(val !== null &&
15+
typeof val === "object" &&
16+
"message" in val &&
17+
typeof val.message === "string" &&
18+
val.message.includes(`\x1B`))
19+
);
20+
},
21+
} satisfies SnapshotSerializer;

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

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,56 @@
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+
"./jest-globals": {
53+
"require": {
54+
"types": "./dist/types/jestGlobals.d.ts",
55+
"default": "./dist/cjs/jestGlobals.js"
56+
},
57+
"import": {
58+
"types": "./dist/types/jestGlobals.d.ts",
59+
"default": "./dist/es/jestGlobals.js"
60+
}
61+
},
62+
"./vitest": {
63+
"require": {
64+
"types": "./dist/types/vitest.d.ts",
65+
"default": "./dist/cjs/vitest.js"
66+
},
67+
"import": {
68+
"types": "./dist/types/vitest.d.ts",
69+
"default": "./dist/es/vitest.js"
70+
}
71+
}
72+
},
3973
"files": [
4074
"dist"
4175
],
4276
"dependencies": {
77+
"@vitest/expect": "1.6.0",
4378
"expect": ">28.1.3",
4479
"tslib": "^2.1.0"
4580
},
@@ -49,12 +84,22 @@
4984
"@smithy/types": "1.1.0",
5085
"@types/jest": "29.5.12",
5186
"@types/sinon": "^10.0.10",
87+
"@vitest/coverage-v8": "^1.6.0",
5288
"aws-sdk-client-mock": "workspace:*",
89+
"chalk": "^5.3.0",
5390
"expect": "29.7.0",
54-
"jest-serializer-ansi-escapes": "3.0.0"
91+
"jest-serializer-ansi-escapes": "3.0.0",
92+
"pretty-ansi": "^2.0.0",
93+
"vitest": "^1.6.0"
5594
},
5695
"peerDependencies": {
57-
"aws-sdk-client-mock": "workspace:*"
96+
"aws-sdk-client-mock": "workspace:*",
97+
"vitest": ">1.3.0"
98+
},
99+
"peerDependenciesMeta": {
100+
"vitest": {
101+
"optional": true
102+
}
58103
},
59104
"jest": {
60105
"preset": "ts-jest",

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

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* eslint-disable @typescript-eslint/no-empty-interface */
2+
import './jest';
3+
import { AwsSdkMockMatchers } from './jestMatchers';
4+
5+
/**
6+
* Types for @jest/globals
7+
*/
8+
declare module 'expect' {
9+
interface Matchers<R = void> extends AwsSdkMockMatchers<R> {}
10+
}

0 commit comments

Comments
 (0)