Skip to content

Commit

Permalink
feat: migrate Jest matchers to @jest/globals types (#198)
Browse files Browse the repository at this point in the history
Jest now includes own types in @jest/globals packages.
Use it for custom Jest matchers types.
Add declaration exposing custom matchers
in the "expect" library types.

Fixes #180
  • Loading branch information
m-radzikowski authored Jan 1, 2024
1 parent d6b6b4d commit 8f14cbb
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 92 deletions.
5 changes: 4 additions & 1 deletion packages/aws-sdk-client-mock-jest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@
"dist"
],
"dependencies": {
"@types/jest": "^28.1.3",
"expect": ">28.1.3",
"tslib": "^2.1.0"
},
"devDependencies": {
"@aws-sdk/client-sns": "3.363.0",
"@jest/globals": "29.7.0",
"@smithy/types": "1.1.0",
"@types/jest": "29.5.11",
"aws-sdk-client-mock": "workspace:*",
"expect": "29.7.0",
"jest-serializer-ansi-escapes": "2.0.1"
},
"peerDependencies": {
Expand Down
59 changes: 36 additions & 23 deletions packages/aws-sdk-client-mock-jest/src/jestMatchers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-interface */
import assert from 'assert';
import type {MetadataBearer} from '@smithy/types';
import type {AwsCommand, AwsStub} from 'aws-sdk-client-mock';
import {AwsCommand, AwsStub} from 'aws-sdk-client-mock';
import type {SinonSpyCall} from 'sinon';
import {expect} from 'expect';
import type {ExpectationResult, MatcherContext, MatcherFunction} from 'expect';

interface AwsSdkJestMockBaseMatchers<R> extends Record<string, any> {
interface AwsSdkJestMockBaseMatchers<R> extends Record<string, Function> {
/**
* Asserts {@link AwsStub Aws Client Mock} received a {@link command} exact number of {@link times}
*
Expand Down Expand Up @@ -69,7 +71,7 @@ interface AwsSdkJestMockBaseMatchers<R> extends Record<string, any> {
): R;
}

interface AwsSdkJestMockAliasMatchers<R> {
interface AwsSdkJestMockAliasMatchers<R> extends Record<string, Function> {
/**
* Asserts {@link AwsStub Aws Client Mock} received a {@link command} exact number of {@link times}
*
Expand Down Expand Up @@ -162,17 +164,27 @@ interface AwsSdkJestMockAliasMatchers<R> {
* });
* ```
*/
export interface AwsSdkJestMockMatchers<R> extends AwsSdkJestMockBaseMatchers<R>, AwsSdkJestMockAliasMatchers<R>, Record<string, any> {
export interface AwsSdkJestMockMatchers<R> extends AwsSdkJestMockBaseMatchers<R>, AwsSdkJestMockAliasMatchers<R>, Record<string, Function> {
}

/**
* Types for @types/jest
*/
declare global {
namespace jest {
interface Matchers<R = void> extends AwsSdkJestMockMatchers<R> {
}
}
}

type ClientMock = AwsStub<any, any, any>;
/**
* Types for @jest/globals
*/
declare module 'expect' {
interface Matchers<R = void> extends AwsSdkJestMockMatchers<R> {
}
}

type AnyCommand = AwsCommand<any, any>;
type AnySpyCall = SinonSpyCall<[AnyCommand]>;
type MessageFunctionParams<CheckData> = {
Expand All @@ -186,7 +198,7 @@ type MessageFunctionParams<CheckData> = {
/**
* Prettyprints command calls for message
*/
const printCalls = (ctx: jest.MatcherContext, calls: AnySpyCall[]): string[] =>
const printCalls = (ctx: MatcherContext, calls: AnySpyCall[]): string[] =>
calls.length > 0
? [
'',
Expand All @@ -200,15 +212,16 @@ const printCalls = (ctx: jest.MatcherContext, calls: AnySpyCall[]): string[] =>
: [];

const processMatch = <CheckData = undefined>({ctx, mockClient, command, check, message}: {
ctx: jest.MatcherContext;
mockClient: ClientMock;
ctx: MatcherContext;
mockClient: unknown;
command: new () => AnyCommand;
check: (params: { calls: AnySpyCall[]; commandCalls: AnySpyCall[] }) => {
pass: boolean;
data: CheckData;
};
message: (params: MessageFunctionParams<CheckData>) => string[];
}): jest.CustomMatcherResult => {
}): ExpectationResult => {
assert(mockClient instanceof AwsStub, 'The actual must be a client mock instance');
assert(
command &&
typeof command === 'function' &&
Expand Down Expand Up @@ -241,13 +254,13 @@ const processMatch = <CheckData = undefined>({ctx, mockClient, command, check, m
return {pass, message: msg};
};

const baseMatchers: { [P in keyof AwsSdkJestMockBaseMatchers<unknown>]: jest.CustomMatcher } = {
const baseMatchers: { [P in keyof AwsSdkJestMockBaseMatchers<unknown>]: MatcherFunction<any[]> } = {
/**
* implementation of {@link AwsSdkJestMockMatchers.toHaveReceivedCommand} matcher
*/
toHaveReceivedCommand(
this: jest.MatcherContext,
mockClient: ClientMock,
this: MatcherContext,
mockClient: unknown,
command: new () => AnyCommand,
) {
return processMatch({
Expand All @@ -265,8 +278,8 @@ const baseMatchers: { [P in keyof AwsSdkJestMockBaseMatchers<unknown>]: jest.Cus
* implementation of {@link AwsSdkJestMockMatchers.toHaveReceivedCommandTimes} matcher
*/
toHaveReceivedCommandTimes(
this: jest.MatcherContext,
mockClient: ClientMock,
this: MatcherContext,
mockClient: unknown,
command: new () => AnyCommand,
expectedCalls: number,
) {
Expand All @@ -285,8 +298,8 @@ const baseMatchers: { [P in keyof AwsSdkJestMockBaseMatchers<unknown>]: jest.Cus
* implementation of {@link AwsSdkJestMockMatchers.toHaveReceivedCommandWith} matcher
*/
toHaveReceivedCommandWith(
this: jest.MatcherContext,
mockClient: ClientMock,
this: MatcherContext,
mockClient: unknown,
command: new () => AnyCommand,
input: Record<string, unknown>,
) {
Expand Down Expand Up @@ -321,11 +334,11 @@ const baseMatchers: { [P in keyof AwsSdkJestMockBaseMatchers<unknown>]: jest.Cus
* implementation of {@link AwsSdkJestMockMatchers.toHaveReceivedNthCommandWith} matcher
*/
toHaveReceivedNthCommandWith(
this: jest.MatcherContext,
mockClient: ClientMock,
this: MatcherContext,
mockClient: unknown,
call: number,
command: new () => AnyCommand,
input?: Record<string, unknown>,
input: Record<string, unknown>,
) {
assert(
call && typeof call === 'number' && call > 0,
Expand Down Expand Up @@ -380,11 +393,11 @@ const baseMatchers: { [P in keyof AwsSdkJestMockBaseMatchers<unknown>]: jest.Cus
* implementation of {@link AwsSdkJestMockMatchers.toHaveReceivedNthSpecificCommandWith} matcher
*/
toHaveReceivedNthSpecificCommandWith(
this: jest.MatcherContext,
mockClient: ClientMock,
this: MatcherContext,
mockClient: unknown,
call: number,
command: new () => AnyCommand,
input?: Record<string, unknown>,
input: Record<string, unknown>,
) {
assert(
call && typeof call === 'number' && call > 0,
Expand Down Expand Up @@ -438,7 +451,7 @@ const baseMatchers: { [P in keyof AwsSdkJestMockBaseMatchers<unknown>]: jest.Cus
};

/* typing ensures keys matching */
const aliasMatchers: { [P in keyof AwsSdkJestMockAliasMatchers<unknown>]: jest.CustomMatcher } = {
const aliasMatchers: { [P in keyof AwsSdkJestMockAliasMatchers<unknown>]: MatcherFunction<any[]> } = {
toReceiveCommandTimes: baseMatchers.toHaveReceivedCommandTimes,
toReceiveCommand: baseMatchers.toHaveReceivedCommand,
toReceiveCommandWith: baseMatchers.toHaveReceivedCommandWith,
Expand Down
5 changes: 5 additions & 0 deletions packages/aws-sdk-client-mock-jest/test-d/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {mockClient} from 'aws-sdk-client-mock';
import {PublishCommand, SNSClient} from '@aws-sdk/client-sns';
import {expect as globalsExpect} from '@jest/globals';
import {expectError} from 'tsd';
import '../src';

expect(1).toHaveReceivedCommand(PublishCommand);

expect(mockClient(SNSClient)).toHaveReceivedCommand(PublishCommand);
expectError(expect(mockClient(SNSClient)).toHaveReceivedCommand(String));

Expand All @@ -14,3 +17,5 @@ expectError(expect(mockClient(SNSClient)).toHaveReceivedCommandWith(PublishComma

expect(mockClient(SNSClient)).toHaveReceivedNthCommandWith(1, PublishCommand, {Message: ''});
expectError(expect(mockClient(SNSClient)).toHaveReceivedNthCommandWith(1, PublishCommand, {Foo: ''}));

globalsExpect(mockClient(SNSClient)).toHaveReceivedCommand(PublishCommand);
14 changes: 14 additions & 0 deletions packages/aws-sdk-client-mock-jest/test/jestGlobals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {mockClient} from 'aws-sdk-client-mock';
import {PublishCommand, SNSClient} from '@aws-sdk/client-sns';
import {publishCmd1} from 'aws-sdk-client-mock/test/fixtures';
import {expect, it} from '@jest/globals';
import '../src';

const snsMock = mockClient(SNSClient);

it('passes using @jest/globals', async () => {
const sns = new SNSClient({});
await sns.send(publishCmd1);

expect(() => expect(snsMock).toHaveReceivedCommand(PublishCommand)).not.toThrow();
});
Loading

0 comments on commit 8f14cbb

Please sign in to comment.