aws-sdk-client-mock
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -5,4 +5,15 @@ # Changelog | ||
## 0.2.0 (2021-03-06) | ||
### ⚠ BREAKING CHANGES | ||
* type-checking of resolved responses (#2) | ||
### Features | ||
* type-checking of resolved responses ([#2](https://github.com/m-radzikowski/aws-sdk-client-mock/issues/2)) ([da31c40](https://github.com/m-radzikowski/aws-sdk-client-mock/commit/da31c40329fd53c4b5d9debd34413662ddbdc26e)) | ||
## 0.1.1 (2021-02-18) | ||
Initial release |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.AwsClientStub = void 0; | ||
exports.CommandBehavior = exports.AwsStub = void 0; | ||
const sinon_1 = require("sinon"); | ||
/** | ||
* Wrapper on the mocked `Client#send()` method, | ||
* allowing to configure it's behavior. | ||
* allowing to configure its behavior. | ||
* | ||
* Without any configuration, `Client#send()` invocation returns `undefined`. | ||
* | ||
* To define resulting variable type easily, use {@link AwsClientStub}. | ||
*/ | ||
class AwsClientStub { | ||
class AwsStub { | ||
constructor(send) { | ||
this.commandMatcher = sinon_1.match.any; | ||
this.inputMatcher = sinon_1.match.any; | ||
this.send = send; | ||
this.anyCommandBehavior = new CommandBehavior(this, send); | ||
} | ||
/** | ||
* Resets stub's history and behavior. | ||
*/ | ||
/** Resets stub's history and behavior. */ | ||
reset() { | ||
@@ -24,5 +23,3 @@ this.send.reset(); | ||
} | ||
/** | ||
* Resets stub's behavior. | ||
*/ | ||
/** Resets stub's behavior. */ | ||
resetBehavior() { | ||
@@ -32,5 +29,3 @@ this.send.resetBehavior(); | ||
} | ||
/** | ||
* Resets stub's calls history. | ||
*/ | ||
/** Resets stub's calls history. */ | ||
resetHistory() { | ||
@@ -40,5 +35,3 @@ this.send.resetHistory(); | ||
} | ||
/** | ||
* Replaces stub with original `Client#send()` method. | ||
*/ | ||
/** Replaces stub with original `Client#send()` method. */ | ||
restore() { | ||
@@ -50,3 +43,2 @@ this.send.restore(); | ||
* Clear history with {@link resetHistory} or {@link reset}. | ||
* @return Array of calls | ||
*/ | ||
@@ -58,3 +50,2 @@ calls() { | ||
* Returns n-th recorded call to the stub. | ||
* @return Call | ||
*/ | ||
@@ -65,6 +56,5 @@ call(n) { | ||
/** | ||
* Specifies a Command type and its input (parameters) for which the next defined `Client#send()` response | ||
* (with {@link resolves} or {@link rejects}) will act. | ||
* Allows specifying the behavior for a given Command type and its input (parameters). | ||
* | ||
* If payload is not specified, it will match any Command of this type. | ||
* If the input is not specified, it will match any Command of that type. | ||
* @param command Command type to match | ||
@@ -74,42 +64,66 @@ * @param input Command payload to (strictly) match | ||
on(command, input) { | ||
this.resetMatchers(); | ||
this.commandMatcher = sinon_1.match.instanceOf(command); | ||
if (input !== undefined) { | ||
this.inputMatcher = sinon_1.match.has('input', input); | ||
} | ||
return this; | ||
const matcher = sinon_1.match.instanceOf(command).and(this.createInputMatcher(input)); | ||
const cmdStub = this.send.withArgs(matcher); | ||
return new CommandBehavior(this, cmdStub); | ||
} | ||
/** | ||
* Specifies a Command input (parameters) for which the next defined `Client#send()` response | ||
* (with {@link resolves} or {@link rejects}) will act. | ||
* Allows specifying the behavior for any Command with given input (parameters). | ||
* | ||
* If input is not specified, the next defined `Client#send()` response will act for any Command and any input. | ||
* This is only for readability, as the result is the same as with not calling it at all. | ||
* If the input is not specified, the given behavior will be used for any Command with any input. | ||
* This is no different from using {@link resolves}, {@link rejects}, etc. directly, | ||
* but can be used for readability. | ||
* @param input Command payload to (strictly) match | ||
*/ | ||
onAnyCommand(input) { | ||
this.resetMatchers(); | ||
this.inputMatcher = input !== undefined ? sinon_1.match.has('input', input) : sinon_1.match.any; | ||
return this; | ||
const cmdStub = this.send.withArgs(this.createInputMatcher(input)); | ||
return new CommandBehavior(this, cmdStub); | ||
} | ||
createInputMatcher(input) { | ||
return input !== undefined ? sinon_1.match.has('input', input) : sinon_1.match.any; | ||
} | ||
/** | ||
* Sets a successful response that will be returned from the `Client#send()` invocation. | ||
* | ||
* If a Command and/or its input were specified (with {@link on} or {@link onAnyCommand}), | ||
* the response will be returned only on receiving them as `Client#send()` argument. | ||
* @param obj Content to be returned from `Client#send()` method | ||
* Sets a successful response that will be returned from any `Client#send()` invocation. | ||
* @param response Content to be returned | ||
*/ | ||
resolves(obj) { | ||
this.prepareStub().resolves(obj); | ||
this.resetMatchers(); | ||
return this; | ||
resolves(response) { | ||
return this.anyCommandBehavior.resolves(response); | ||
} | ||
/** | ||
* Sets a failure response that will be returned from the `Client#send()` invocation. | ||
* Sets a failure response that will be returned from any `Client#send()` invocation. | ||
* The response will always be an `Error` instance. | ||
* | ||
* The same Command and its input rules apply as in the {@link resolves}. | ||
* @param error Error text, Error instance or Error parameters to be returned from `Client#send()` method | ||
* @param error Error text, Error instance or Error parameters to be returned | ||
*/ | ||
rejects(error) { | ||
return this.anyCommandBehavior.rejects(error); | ||
} | ||
/** | ||
* Sets a function that will be called on any `Client#send()` invocation. | ||
* @param fn Function taking Command input and returning result | ||
*/ | ||
callsFake(fn) { | ||
return this.anyCommandBehavior.callsFake(fn); | ||
} | ||
} | ||
exports.AwsStub = AwsStub; | ||
class CommandBehavior { | ||
constructor(clientStub, send) { | ||
this.clientStub = clientStub; | ||
this.send = send; | ||
} | ||
/** | ||
* Sets a successful response that will be returned from the `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* @param response Content to be returned | ||
*/ | ||
resolves(response) { | ||
this.send.resolves(response); | ||
return this.clientStub; | ||
} | ||
/** | ||
* Sets a failure response that will be returned from the `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* The response will always be an `Error` instance. | ||
* @param error Error text, Error instance or Error parameters to be returned | ||
*/ | ||
rejects(error) { | ||
if (typeof error === 'string') { | ||
@@ -121,32 +135,16 @@ error = new Error(error); | ||
} | ||
this.prepareStub().rejects(error); | ||
this.resetMatchers(); | ||
return this; | ||
this.send.rejects(error); | ||
return this.clientStub; | ||
} | ||
/** | ||
* Sets a function that will be called on `Client#send()` invocation. | ||
* | ||
* The same Command and its input rules apply as in the {@link resolves}. | ||
* Sets a function that will be called on `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* @param fn Function taking Command input and returning result | ||
*/ | ||
callsFake(fn) { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
this.prepareStub().callsFake(cmd => fn(cmd.input)); | ||
this.resetMatchers(); | ||
return this; | ||
this.send.callsFake(cmd => fn(cmd.input)); | ||
return this.clientStub; | ||
} | ||
prepareStub() { | ||
return this.send.withArgs(this.getMatcher()); | ||
} | ||
getMatcher() { | ||
return sinon_1.match.any | ||
.and(this.commandMatcher) | ||
.and(this.inputMatcher); | ||
} | ||
resetMatchers() { | ||
this.commandMatcher = sinon_1.match.any; | ||
this.inputMatcher = sinon_1.match.any; | ||
} | ||
} | ||
exports.AwsClientStub = AwsClientStub; | ||
exports.CommandBehavior = CommandBehavior; | ||
//# sourceMappingURL=awsClientStub.js.map |
@@ -19,6 +19,4 @@ "use strict"; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore // TODO Resolve - see: https://stackoverflow.com/questions/56505560/could-be-instantiated-with-a-different-subtype-of-constraint-object-ts2322 | ||
const sendStub = sinon_1.stub(instance, 'send'); | ||
return new awsClientStub_1.AwsClientStub(sendStub); | ||
return new awsClientStub_1.AwsStub(sendStub); | ||
}; | ||
@@ -25,0 +23,0 @@ exports.mockClient = mockClient; |
import { match } from 'sinon'; | ||
/** | ||
* Wrapper on the mocked `Client#send()` method, | ||
* allowing to configure it's behavior. | ||
* allowing to configure its behavior. | ||
* | ||
* Without any configuration, `Client#send()` invocation returns `undefined`. | ||
* | ||
* To define resulting variable type easily, use {@link AwsClientStub}. | ||
*/ | ||
var AwsClientStub = /** @class */ (function () { | ||
function AwsClientStub(send) { | ||
this.commandMatcher = match.any; | ||
this.inputMatcher = match.any; | ||
var AwsStub = /** @class */ (function () { | ||
function AwsStub(send) { | ||
this.send = send; | ||
this.anyCommandBehavior = new CommandBehavior(this, send); | ||
} | ||
/** | ||
* Resets stub's history and behavior. | ||
*/ | ||
AwsClientStub.prototype.reset = function () { | ||
/** Resets stub's history and behavior. */ | ||
AwsStub.prototype.reset = function () { | ||
this.send.reset(); | ||
return this; | ||
}; | ||
/** | ||
* Resets stub's behavior. | ||
*/ | ||
AwsClientStub.prototype.resetBehavior = function () { | ||
/** Resets stub's behavior. */ | ||
AwsStub.prototype.resetBehavior = function () { | ||
this.send.resetBehavior(); | ||
return this; | ||
}; | ||
/** | ||
* Resets stub's calls history. | ||
*/ | ||
AwsClientStub.prototype.resetHistory = function () { | ||
/** Resets stub's calls history. */ | ||
AwsStub.prototype.resetHistory = function () { | ||
this.send.resetHistory(); | ||
return this; | ||
}; | ||
/** | ||
* Replaces stub with original `Client#send()` method. | ||
*/ | ||
AwsClientStub.prototype.restore = function () { | ||
/** Replaces stub with original `Client#send()` method. */ | ||
AwsStub.prototype.restore = function () { | ||
this.send.restore(); | ||
@@ -44,5 +37,4 @@ }; | ||
* Clear history with {@link resetHistory} or {@link reset}. | ||
* @return Array of calls | ||
*/ | ||
AwsClientStub.prototype.calls = function () { | ||
AwsStub.prototype.calls = function () { | ||
return this.send.getCalls(); | ||
@@ -52,56 +44,79 @@ }; | ||
* Returns n-th recorded call to the stub. | ||
* @return Call | ||
*/ | ||
AwsClientStub.prototype.call = function (n) { | ||
AwsStub.prototype.call = function (n) { | ||
return this.send.getCall(n); | ||
}; | ||
/** | ||
* Specifies a Command type and its input (parameters) for which the next defined `Client#send()` response | ||
* (with {@link resolves} or {@link rejects}) will act. | ||
* Allows specifying the behavior for a given Command type and its input (parameters). | ||
* | ||
* If payload is not specified, it will match any Command of this type. | ||
* If the input is not specified, it will match any Command of that type. | ||
* @param command Command type to match | ||
* @param input Command payload to (strictly) match | ||
*/ | ||
AwsClientStub.prototype.on = function (command, input) { | ||
this.resetMatchers(); | ||
this.commandMatcher = match.instanceOf(command); | ||
if (input !== undefined) { | ||
this.inputMatcher = match.has('input', input); | ||
} | ||
return this; | ||
AwsStub.prototype.on = function (command, input) { | ||
var matcher = match.instanceOf(command).and(this.createInputMatcher(input)); | ||
var cmdStub = this.send.withArgs(matcher); | ||
return new CommandBehavior(this, cmdStub); | ||
}; | ||
/** | ||
* Specifies a Command input (parameters) for which the next defined `Client#send()` response | ||
* (with {@link resolves} or {@link rejects}) will act. | ||
* Allows specifying the behavior for any Command with given input (parameters). | ||
* | ||
* If input is not specified, the next defined `Client#send()` response will act for any Command and any input. | ||
* This is only for readability, as the result is the same as with not calling it at all. | ||
* If the input is not specified, the given behavior will be used for any Command with any input. | ||
* This is no different from using {@link resolves}, {@link rejects}, etc. directly, | ||
* but can be used for readability. | ||
* @param input Command payload to (strictly) match | ||
*/ | ||
AwsClientStub.prototype.onAnyCommand = function (input) { | ||
this.resetMatchers(); | ||
this.inputMatcher = input !== undefined ? match.has('input', input) : match.any; | ||
return this; | ||
AwsStub.prototype.onAnyCommand = function (input) { | ||
var cmdStub = this.send.withArgs(this.createInputMatcher(input)); | ||
return new CommandBehavior(this, cmdStub); | ||
}; | ||
AwsStub.prototype.createInputMatcher = function (input) { | ||
return input !== undefined ? match.has('input', input) : match.any; | ||
}; | ||
/** | ||
* Sets a successful response that will be returned from the `Client#send()` invocation. | ||
* | ||
* If a Command and/or its input were specified (with {@link on} or {@link onAnyCommand}), | ||
* the response will be returned only on receiving them as `Client#send()` argument. | ||
* @param obj Content to be returned from `Client#send()` method | ||
* Sets a successful response that will be returned from any `Client#send()` invocation. | ||
* @param response Content to be returned | ||
*/ | ||
AwsClientStub.prototype.resolves = function (obj) { | ||
this.prepareStub().resolves(obj); | ||
this.resetMatchers(); | ||
return this; | ||
AwsStub.prototype.resolves = function (response) { | ||
return this.anyCommandBehavior.resolves(response); | ||
}; | ||
/** | ||
* Sets a failure response that will be returned from the `Client#send()` invocation. | ||
* Sets a failure response that will be returned from any `Client#send()` invocation. | ||
* The response will always be an `Error` instance. | ||
* | ||
* The same Command and its input rules apply as in the {@link resolves}. | ||
* @param error Error text, Error instance or Error parameters to be returned from `Client#send()` method | ||
* @param error Error text, Error instance or Error parameters to be returned | ||
*/ | ||
AwsClientStub.prototype.rejects = function (error) { | ||
AwsStub.prototype.rejects = function (error) { | ||
return this.anyCommandBehavior.rejects(error); | ||
}; | ||
/** | ||
* Sets a function that will be called on any `Client#send()` invocation. | ||
* @param fn Function taking Command input and returning result | ||
*/ | ||
AwsStub.prototype.callsFake = function (fn) { | ||
return this.anyCommandBehavior.callsFake(fn); | ||
}; | ||
return AwsStub; | ||
}()); | ||
export { AwsStub }; | ||
var CommandBehavior = /** @class */ (function () { | ||
function CommandBehavior(clientStub, send) { | ||
this.clientStub = clientStub; | ||
this.send = send; | ||
} | ||
/** | ||
* Sets a successful response that will be returned from the `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* @param response Content to be returned | ||
*/ | ||
CommandBehavior.prototype.resolves = function (response) { | ||
this.send.resolves(response); | ||
return this.clientStub; | ||
}; | ||
/** | ||
* Sets a failure response that will be returned from the `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* The response will always be an `Error` instance. | ||
* @param error Error text, Error instance or Error parameters to be returned | ||
*/ | ||
CommandBehavior.prototype.rejects = function (error) { | ||
if (typeof error === 'string') { | ||
@@ -113,33 +128,17 @@ error = new Error(error); | ||
} | ||
this.prepareStub().rejects(error); | ||
this.resetMatchers(); | ||
return this; | ||
this.send.rejects(error); | ||
return this.clientStub; | ||
}; | ||
/** | ||
* Sets a function that will be called on `Client#send()` invocation. | ||
* | ||
* The same Command and its input rules apply as in the {@link resolves}. | ||
* Sets a function that will be called on `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* @param fn Function taking Command input and returning result | ||
*/ | ||
AwsClientStub.prototype.callsFake = function (fn) { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
this.prepareStub().callsFake(function (cmd) { return fn(cmd.input); }); | ||
this.resetMatchers(); | ||
return this; | ||
CommandBehavior.prototype.callsFake = function (fn) { | ||
this.send.callsFake(function (cmd) { return fn(cmd.input); }); | ||
return this.clientStub; | ||
}; | ||
AwsClientStub.prototype.prepareStub = function () { | ||
return this.send.withArgs(this.getMatcher()); | ||
}; | ||
AwsClientStub.prototype.getMatcher = function () { | ||
return match.any | ||
.and(this.commandMatcher) | ||
.and(this.inputMatcher); | ||
}; | ||
AwsClientStub.prototype.resetMatchers = function () { | ||
this.commandMatcher = match.any; | ||
this.inputMatcher = match.any; | ||
}; | ||
return AwsClientStub; | ||
return CommandBehavior; | ||
}()); | ||
export { AwsClientStub }; | ||
export { CommandBehavior }; | ||
//# sourceMappingURL=awsClientStub.js.map |
import { stub } from 'sinon'; | ||
import { isSinonStub } from './sinon'; | ||
import { AwsClientStub } from './awsClientStub'; | ||
import { AwsStub } from './awsClientStub'; | ||
/** | ||
@@ -16,6 +16,4 @@ * Creates and attaches a stub of the `Client#send()` method. Only this single method is mocked. | ||
} | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore // TODO Resolve - see: https://stackoverflow.com/questions/56505560/could-be-instantiated-with-a-different-subtype-of-constraint-object-ts2322 | ||
var sendStub = stub(instance, 'send'); | ||
return new AwsClientStub(sendStub); | ||
return new AwsStub(sendStub); | ||
}; | ||
@@ -22,0 +20,0 @@ /** |
@@ -1,10 +0,29 @@ | ||
import { Client, MetadataBearer } from '@aws-sdk/types'; | ||
import { Client, Command, MetadataBearer } from '@aws-sdk/types'; | ||
import { SinonSpyCall, SinonStub } from 'sinon'; | ||
export declare type AwsClientBehavior<TClient extends Client<any, any, any>> = TClient extends Client<infer TInput, infer TOutput, any> ? Behavior<TInput, TOutput, TOutput> : never; | ||
interface Behavior<TInput extends object, TOutput extends MetadataBearer, TCommandOutput extends TOutput> { | ||
resolves(response: CommandResponse<TCommandOutput>): AwsStub<TInput, TOutput>; | ||
rejects(error?: string | Error | AwsError): AwsStub<TInput, TOutput>; | ||
callsFake(fn: (input: any) => any): AwsStub<TInput, TOutput>; | ||
} | ||
/** | ||
* Type for {@link AwsStub} class, | ||
* but with the AWS Client class type as an only generic parameter. | ||
* | ||
* Usage: | ||
* ```typescript | ||
* let snsMock: AwsClientStub<SNSClient>; | ||
* snsMock = mockClient(SNSClient); | ||
* ``` | ||
*/ | ||
export declare type AwsClientStub<TClient extends Client<any, any, any>> = TClient extends Client<infer TInput, infer TOutput, any> ? AwsStub<TInput, TOutput> : never; | ||
/** | ||
* Wrapper on the mocked `Client#send()` method, | ||
* allowing to configure it's behavior. | ||
* allowing to configure its behavior. | ||
* | ||
* Without any configuration, `Client#send()` invocation returns `undefined`. | ||
* | ||
* To define resulting variable type easily, use {@link AwsClientStub}. | ||
*/ | ||
export declare class AwsClientStub<TClient extends Client<any, any, any>> { | ||
export declare class AwsStub<TInput extends object, TOutput extends MetadataBearer> implements Behavior<TInput, TOutput, TOutput> { | ||
/** | ||
@@ -15,21 +34,12 @@ * Underlying `Client#send()` method Sinon stub. | ||
*/ | ||
send: SinonStub<[AwsCommand<TClient>], unknown>; | ||
private commandMatcher; | ||
private inputMatcher; | ||
constructor(send: SinonStub<[AwsCommand<TClient>], unknown>); | ||
/** | ||
* Resets stub's history and behavior. | ||
*/ | ||
reset(): AwsClientStub<TClient>; | ||
/** | ||
* Resets stub's behavior. | ||
*/ | ||
resetBehavior(): AwsClientStub<TClient>; | ||
/** | ||
* Resets stub's calls history. | ||
*/ | ||
resetHistory(): AwsClientStub<TClient>; | ||
/** | ||
* Replaces stub with original `Client#send()` method. | ||
*/ | ||
send: SinonStub<[AwsCommand<TInput, TOutput>], unknown>; | ||
private readonly anyCommandBehavior; | ||
constructor(send: SinonStub<[AwsCommand<TInput, TOutput>], unknown>); | ||
/** Resets stub's history and behavior. */ | ||
reset(): AwsStub<TInput, TOutput>; | ||
/** Resets stub's behavior. */ | ||
resetBehavior(): AwsStub<TInput, TOutput>; | ||
/** Resets stub's calls history. */ | ||
resetHistory(): AwsStub<TInput, TOutput>; | ||
/** Replaces stub with original `Client#send()` method. */ | ||
restore(): void; | ||
@@ -39,69 +49,69 @@ /** | ||
* Clear history with {@link resetHistory} or {@link reset}. | ||
* @return Array of calls | ||
*/ | ||
calls(): SinonSpyCall<[AwsCommand<TClient>], unknown>[]; | ||
calls(): SinonSpyCall<[AwsCommand<TInput, TOutput>], unknown>[]; | ||
/** | ||
* Returns n-th recorded call to the stub. | ||
* @return Call | ||
*/ | ||
call(n: number): SinonSpyCall<[AwsCommand<TClient>], unknown>; | ||
call(n: number): SinonSpyCall<[AwsCommand<TInput, TOutput>], unknown>; | ||
/** | ||
* Specifies a Command type and its input (parameters) for which the next defined `Client#send()` response | ||
* (with {@link resolves} or {@link rejects}) will act. | ||
* Allows specifying the behavior for a given Command type and its input (parameters). | ||
* | ||
* If payload is not specified, it will match any Command of this type. | ||
* If the input is not specified, it will match any Command of that type. | ||
* @param command Command type to match | ||
* @param input Command payload to (strictly) match | ||
*/ | ||
on<TInput>(command: AwsCommandType<TClient, TInput>, input?: TInput): AwsClientStub<TClient>; | ||
on<TCmdInput extends TInput, TCmdOutput extends TOutput>(command: new (input: TCmdInput) => AwsCommand<TCmdInput, TCmdOutput>, input?: TCmdInput): CommandBehavior<TInput, TOutput, TCmdOutput>; | ||
/** | ||
* Specifies a Command input (parameters) for which the next defined `Client#send()` response | ||
* (with {@link resolves} or {@link rejects}) will act. | ||
* Allows specifying the behavior for any Command with given input (parameters). | ||
* | ||
* If input is not specified, the next defined `Client#send()` response will act for any Command and any input. | ||
* This is only for readability, as the result is the same as with not calling it at all. | ||
* If the input is not specified, the given behavior will be used for any Command with any input. | ||
* This is no different from using {@link resolves}, {@link rejects}, etc. directly, | ||
* but can be used for readability. | ||
* @param input Command payload to (strictly) match | ||
*/ | ||
onAnyCommand(input?: unknown): AwsClientStub<TClient>; | ||
onAnyCommand<TCmdInput extends TInput>(input?: TCmdInput): CommandBehavior<TInput, TOutput, TOutput>; | ||
private createInputMatcher; | ||
/** | ||
* Sets a successful response that will be returned from the `Client#send()` invocation. | ||
* | ||
* If a Command and/or its input were specified (with {@link on} or {@link onAnyCommand}), | ||
* the response will be returned only on receiving them as `Client#send()` argument. | ||
* @param obj Content to be returned from `Client#send()` method | ||
* Sets a successful response that will be returned from any `Client#send()` invocation. | ||
* @param response Content to be returned | ||
*/ | ||
resolves(obj: unknown): AwsClientStub<TClient>; | ||
resolves(response: CommandResponse<TOutput>): AwsStub<TInput, TOutput>; | ||
/** | ||
* Sets a failure response that will be returned from the `Client#send()` invocation. | ||
* Sets a failure response that will be returned from any `Client#send()` invocation. | ||
* The response will always be an `Error` instance. | ||
* | ||
* The same Command and its input rules apply as in the {@link resolves}. | ||
* @param error Error text, Error instance or Error parameters to be returned from `Client#send()` method | ||
* @param error Error text, Error instance or Error parameters to be returned | ||
*/ | ||
rejects(error?: string | Error | AwsError): AwsClientStub<TClient>; | ||
rejects(error?: string | Error | AwsError): AwsStub<TInput, TOutput>; | ||
/** | ||
* Sets a function that will be called on `Client#send()` invocation. | ||
* | ||
* The same Command and its input rules apply as in the {@link resolves}. | ||
* Sets a function that will be called on any `Client#send()` invocation. | ||
* @param fn Function taking Command input and returning result | ||
*/ | ||
callsFake(fn: (input: any) => any): AwsClientStub<TClient>; | ||
private prepareStub; | ||
private getMatcher; | ||
private resetMatchers; | ||
callsFake(fn: (input: any) => any): AwsStub<TInput, TOutput>; | ||
} | ||
/** | ||
* Type matching the Command instances accepted by the Client. | ||
*/ | ||
export declare type AwsCommand<TClient extends Client<any, any, any>> = Parameters<TClient['send']>[0]; | ||
/** | ||
* Type matching the Command type (class) accepted by the Client. | ||
*/ | ||
export declare type AwsCommandType<TClient extends Client<any, any, any>, TInput> = new (input: TInput) => MatchArguments<AwsCommand<TClient>>; | ||
/** | ||
* Based on solution from Sinon sources. | ||
*/ | ||
declare type MatchArguments<T> = { | ||
[K in keyof T]: (T[K] extends object ? MatchArguments<T[K]> : never) | T[K]; | ||
}; | ||
export declare class CommandBehavior<TInput extends object, TOutput extends MetadataBearer, TCommandOutput extends TOutput> implements Behavior<TInput, TOutput, TCommandOutput> { | ||
private clientStub; | ||
private send; | ||
constructor(clientStub: AwsStub<TInput, TOutput>, send: SinonStub<[AwsCommand<TInput, TOutput>], unknown>); | ||
/** | ||
* Sets a successful response that will be returned from the `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* @param response Content to be returned | ||
*/ | ||
resolves(response: CommandResponse<TCommandOutput>): AwsStub<TInput, TOutput>; | ||
/** | ||
* Sets a failure response that will be returned from the `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* The response will always be an `Error` instance. | ||
* @param error Error text, Error instance or Error parameters to be returned | ||
*/ | ||
rejects(error?: string | Error | AwsError): AwsStub<TInput, TOutput>; | ||
/** | ||
* Sets a function that will be called on `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* @param fn Function taking Command input and returning result | ||
*/ | ||
callsFake(fn: (input: any) => any): AwsStub<TInput, TOutput>; | ||
} | ||
declare type AwsCommand<Input extends ClientInput, Output extends ClientOutput, ClientInput extends object = any, ClientOutput extends MetadataBearer = any> = Command<ClientInput, Input, ClientOutput, Output, any>; | ||
declare type CommandResponse<TOutput> = Partial<TOutput> | PromiseLike<Partial<TOutput>>; | ||
export interface AwsError extends Partial<Error>, Partial<MetadataBearer> { | ||
@@ -108,0 +118,0 @@ Type?: string; |
@@ -1,3 +0,3 @@ | ||
import { Client } from '@aws-sdk/types'; | ||
import { AwsClientStub } from './awsClientStub'; | ||
import { Client, MetadataBearer } from '@aws-sdk/types'; | ||
import { AwsStub } from './awsClientStub'; | ||
/** | ||
@@ -9,8 +9,8 @@ * Creates and attaches a stub of the `Client#send()` method. Only this single method is mocked. | ||
*/ | ||
export declare const mockClient: <TClient extends AwsClient>(client: TClient | Constructor<TClient>) => AwsClientStub<TClient>; | ||
declare type Constructor<T> = { | ||
export declare const mockClient: <TInput extends object, TOutput extends MetadataBearer>(client: InstanceOrClassType<Client<TInput, TOutput, any>>) => AwsStub<TInput, TOutput>; | ||
declare type ClassType<T> = { | ||
new (...args: never[]): T; | ||
prototype: T; | ||
}; | ||
declare type AwsClient = Client<any, any, any>; | ||
declare type InstanceOrClassType<T> = T | ClassType<T>; | ||
export {}; |
{ | ||
"name": "aws-sdk-client-mock", | ||
"description": "Easy and powerful mocking of AWS SDK v3 Clients", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "author": { |
@@ -21,2 +21,6 @@ <div align="center"> | ||
In action: | ||
 | ||
### Table of Contents | ||
@@ -139,5 +143,56 @@ | ||
Specify mock behavior on receiving given payload only: | ||
```typescript | ||
snsMock | ||
.onAnyCommand({ | ||
TopicArn: 'arn:aws:sns:us-east-1:111111111111:MyTopic', | ||
Message: 'My message', | ||
}) | ||
.resolves({ | ||
MessageId: '12345678-4444-5555-6666-111122223333', | ||
}); | ||
``` | ||
Multiple behaviors (for different commands and payloads) may be specified | ||
for a single mock. | ||
for a single mock: | ||
```typescript | ||
snsMock | ||
.resolves({ // default for any command | ||
MessageId: '12345678-1111-2222-3333-111122223333' | ||
}) | ||
.on(PublishCommand) | ||
.resolves({ // default for PublishCommand | ||
MessageId: '12345678-4444-5555-6666-111122223333' | ||
}) | ||
.on(PublishCommand, { | ||
TopicArn: 'arn:aws:sns:us-east-1:111111111111:MyTopic', | ||
Message: 'My message', | ||
}) | ||
.resolves({ // for PublishCommand with given input | ||
MessageId: '12345678-7777-8888-9999-111122223333', | ||
}); | ||
``` | ||
Specify mock throwing an error: | ||
```typescript | ||
snsMock | ||
.rejects('mocked rejection'); | ||
``` | ||
Specify custom mock function: | ||
```typescript | ||
snsMock | ||
.callsFake(input => { | ||
if (input.Message === 'My message') { | ||
return {MessageId: '12345678-1111-2222-3333-111122223333'}; | ||
} else { | ||
return {MessageId: '12345678-4444-5555-6666-111122223333'}; | ||
} | ||
}); | ||
``` | ||
Inspect received calls: | ||
@@ -210,3 +265,3 @@ | ||
it('message IDs are returned', async () => { | ||
snsMock.on(PublishCommand, { | ||
snsMock.on(PublishCommand).resolves({ | ||
MessageId: '12345678-1111-2222-3333-111122223333', | ||
@@ -224,3 +279,3 @@ }); | ||
it('SNS Client is called', async () => { | ||
snsMock.on(PublishCommand, { | ||
snsMock.on(PublishCommand).resolves({ | ||
MessageId: '111-222-333', | ||
@@ -227,0 +282,0 @@ }); |
@@ -1,11 +0,39 @@ | ||
import {Client, MetadataBearer} from '@aws-sdk/types'; | ||
import {match, SinonMatcher, SinonSpyCall, SinonStub} from 'sinon'; | ||
import {Client, Command, MetadataBearer} from '@aws-sdk/types'; | ||
import {match, SinonSpyCall, SinonStub} from 'sinon'; | ||
export type AwsClientBehavior<TClient extends Client<any, any, any>> = | ||
TClient extends Client<infer TInput, infer TOutput, any> ? Behavior<TInput, TOutput, TOutput> : never; | ||
interface Behavior<TInput extends object, TOutput extends MetadataBearer, TCommandOutput extends TOutput> { | ||
resolves(response: CommandResponse<TCommandOutput>): AwsStub<TInput, TOutput>; | ||
rejects(error?: string | Error | AwsError): AwsStub<TInput, TOutput>; | ||
callsFake(fn: (input: any) => any): AwsStub<TInput, TOutput>; // TODO Types | ||
} | ||
/** | ||
* Type for {@link AwsStub} class, | ||
* but with the AWS Client class type as an only generic parameter. | ||
* | ||
* Usage: | ||
* ```typescript | ||
* let snsMock: AwsClientStub<SNSClient>; | ||
* snsMock = mockClient(SNSClient); | ||
* ``` | ||
*/ | ||
export type AwsClientStub<TClient extends Client<any, any, any>> = | ||
TClient extends Client<infer TInput, infer TOutput, any> ? AwsStub<TInput, TOutput> : never; | ||
/** | ||
* Wrapper on the mocked `Client#send()` method, | ||
* allowing to configure it's behavior. | ||
* allowing to configure its behavior. | ||
* | ||
* Without any configuration, `Client#send()` invocation returns `undefined`. | ||
* | ||
* To define resulting variable type easily, use {@link AwsClientStub}. | ||
*/ | ||
export class AwsClientStub<TClient extends Client<any, any, any>> { | ||
export class AwsStub<TInput extends object, TOutput extends MetadataBearer> implements Behavior<TInput, TOutput, TOutput> { | ||
@@ -17,15 +45,13 @@ /** | ||
*/ | ||
public send: SinonStub<[AwsCommand<TClient>], unknown>; | ||
public send: SinonStub<[AwsCommand<TInput, TOutput>], unknown>; | ||
private commandMatcher: SinonMatcher = match.any; | ||
private inputMatcher: SinonMatcher = match.any; | ||
private readonly anyCommandBehavior: CommandBehavior<TInput, TOutput, TOutput>; | ||
constructor(send: SinonStub<[AwsCommand<TClient>], unknown>) { | ||
constructor(send: SinonStub<[AwsCommand<TInput, TOutput>], unknown>) { | ||
this.send = send; | ||
this.anyCommandBehavior = new CommandBehavior(this, send); | ||
} | ||
/** | ||
* Resets stub's history and behavior. | ||
*/ | ||
reset(): AwsClientStub<TClient> { | ||
/** Resets stub's history and behavior. */ | ||
reset(): AwsStub<TInput, TOutput> { | ||
this.send.reset(); | ||
@@ -35,6 +61,4 @@ return this; | ||
/** | ||
* Resets stub's behavior. | ||
*/ | ||
resetBehavior(): AwsClientStub<TClient> { | ||
/** Resets stub's behavior. */ | ||
resetBehavior(): AwsStub<TInput, TOutput> { | ||
this.send.resetBehavior(); | ||
@@ -44,6 +68,4 @@ return this; | ||
/** | ||
* Resets stub's calls history. | ||
*/ | ||
resetHistory(): AwsClientStub<TClient> { | ||
/** Resets stub's calls history. */ | ||
resetHistory(): AwsStub<TInput, TOutput> { | ||
this.send.resetHistory(); | ||
@@ -53,5 +75,3 @@ return this; | ||
/** | ||
* Replaces stub with original `Client#send()` method. | ||
*/ | ||
/** Replaces stub with original `Client#send()` method. */ | ||
restore(): void { | ||
@@ -64,5 +84,4 @@ this.send.restore(); | ||
* Clear history with {@link resetHistory} or {@link reset}. | ||
* @return Array of calls | ||
*/ | ||
calls(): SinonSpyCall<[AwsCommand<TClient>], unknown>[] { | ||
calls(): SinonSpyCall<[AwsCommand<TInput, TOutput>], unknown>[] { | ||
return this.send.getCalls(); | ||
@@ -73,5 +92,4 @@ } | ||
* Returns n-th recorded call to the stub. | ||
* @return Call | ||
*/ | ||
call(n: number): SinonSpyCall<[AwsCommand<TClient>], unknown> { | ||
call(n: number): SinonSpyCall<[AwsCommand<TInput, TOutput>], unknown> { | ||
return this.send.getCall(n); | ||
@@ -81,57 +99,85 @@ } | ||
/** | ||
* Specifies a Command type and its input (parameters) for which the next defined `Client#send()` response | ||
* (with {@link resolves} or {@link rejects}) will act. | ||
* Allows specifying the behavior for a given Command type and its input (parameters). | ||
* | ||
* If payload is not specified, it will match any Command of this type. | ||
* If the input is not specified, it will match any Command of that type. | ||
* @param command Command type to match | ||
* @param input Command payload to (strictly) match | ||
*/ | ||
on<TInput>(command: AwsCommandType<TClient, TInput>, input?: TInput): AwsClientStub<TClient> { | ||
this.resetMatchers(); | ||
this.commandMatcher = match.instanceOf(command); | ||
if (input !== undefined) { | ||
this.inputMatcher = match.has('input', input); | ||
} | ||
return this; | ||
on<TCmdInput extends TInput, TCmdOutput extends TOutput>( | ||
command: new (input: TCmdInput) => AwsCommand<TCmdInput, TCmdOutput>, input?: TCmdInput, | ||
): CommandBehavior<TInput, TOutput, TCmdOutput> { | ||
const matcher = match.instanceOf(command).and(this.createInputMatcher(input)); | ||
const cmdStub = this.send.withArgs(matcher); | ||
return new CommandBehavior<TInput, TOutput, TCmdOutput>(this, cmdStub); | ||
} | ||
/** | ||
* Specifies a Command input (parameters) for which the next defined `Client#send()` response | ||
* (with {@link resolves} or {@link rejects}) will act. | ||
* Allows specifying the behavior for any Command with given input (parameters). | ||
* | ||
* If input is not specified, the next defined `Client#send()` response will act for any Command and any input. | ||
* This is only for readability, as the result is the same as with not calling it at all. | ||
* If the input is not specified, the given behavior will be used for any Command with any input. | ||
* This is no different from using {@link resolves}, {@link rejects}, etc. directly, | ||
* but can be used for readability. | ||
* @param input Command payload to (strictly) match | ||
*/ | ||
onAnyCommand(input?: unknown): AwsClientStub<TClient> { | ||
this.resetMatchers(); | ||
this.inputMatcher = input !== undefined ? match.has('input', input) : match.any; | ||
return this; | ||
onAnyCommand<TCmdInput extends TInput>(input?: TCmdInput): CommandBehavior<TInput, TOutput, TOutput> { | ||
const cmdStub = this.send.withArgs(this.createInputMatcher(input)); | ||
return new CommandBehavior(this, cmdStub); | ||
} | ||
private createInputMatcher<TCmdInput extends TInput>(input?: TCmdInput) { | ||
return input !== undefined ? match.has('input', input) : match.any; | ||
} | ||
/** | ||
* Sets a successful response that will be returned from the `Client#send()` invocation. | ||
* | ||
* If a Command and/or its input were specified (with {@link on} or {@link onAnyCommand}), | ||
* the response will be returned only on receiving them as `Client#send()` argument. | ||
* @param obj Content to be returned from `Client#send()` method | ||
* Sets a successful response that will be returned from any `Client#send()` invocation. | ||
* @param response Content to be returned | ||
*/ | ||
resolves(obj: unknown): AwsClientStub<TClient> { | ||
this.prepareStub().resolves(obj); | ||
resolves(response: CommandResponse<TOutput>): AwsStub<TInput, TOutput> { | ||
return this.anyCommandBehavior.resolves(response); | ||
} | ||
this.resetMatchers(); | ||
/** | ||
* Sets a failure response that will be returned from any `Client#send()` invocation. | ||
* The response will always be an `Error` instance. | ||
* @param error Error text, Error instance or Error parameters to be returned | ||
*/ | ||
rejects(error?: string | Error | AwsError): AwsStub<TInput, TOutput> { | ||
return this.anyCommandBehavior.rejects(error); | ||
} | ||
return this; | ||
/** | ||
* Sets a function that will be called on any `Client#send()` invocation. | ||
* @param fn Function taking Command input and returning result | ||
*/ | ||
callsFake(fn: (input: any) => any): AwsStub<TInput, TOutput> { | ||
return this.anyCommandBehavior.callsFake(fn); | ||
} | ||
} | ||
export class CommandBehavior<TInput extends object, TOutput extends MetadataBearer, TCommandOutput extends TOutput> implements Behavior<TInput, TOutput, TCommandOutput> { | ||
constructor( | ||
private clientStub: AwsStub<TInput, TOutput>, | ||
private send: SinonStub<[AwsCommand<TInput, TOutput>], unknown>, | ||
) { | ||
} | ||
/** | ||
* Sets a failure response that will be returned from the `Client#send()` invocation. | ||
* Sets a successful response that will be returned from the `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* @param response Content to be returned | ||
*/ | ||
resolves(response: CommandResponse<TCommandOutput>): AwsStub<TInput, TOutput> { | ||
this.send.resolves(response); | ||
return this.clientStub; | ||
} | ||
/** | ||
* Sets a failure response that will be returned from the `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* The response will always be an `Error` instance. | ||
* | ||
* The same Command and its input rules apply as in the {@link resolves}. | ||
* @param error Error text, Error instance or Error parameters to be returned from `Client#send()` method | ||
* @param error Error text, Error instance or Error parameters to be returned | ||
*/ | ||
rejects(error?: string | Error | AwsError): AwsClientStub<TClient> { | ||
rejects(error?: string | Error | AwsError): AwsStub<TInput, TOutput> { | ||
if (typeof error === 'string') { | ||
@@ -144,59 +190,22 @@ error = new Error(error); | ||
this.prepareStub().rejects(error); | ||
this.send.rejects(error); | ||
this.resetMatchers(); | ||
return this; | ||
return this.clientStub; | ||
} | ||
/** | ||
* Sets a function that will be called on `Client#send()` invocation. | ||
* | ||
* The same Command and its input rules apply as in the {@link resolves}. | ||
* Sets a function that will be called on `Client#send()` invocation | ||
* for specified Command and/or its input. | ||
* @param fn Function taking Command input and returning result | ||
*/ | ||
callsFake(fn: (input: any) => any): AwsClientStub<TClient> { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
this.prepareStub().callsFake(cmd => fn(cmd.input)); | ||
this.resetMatchers(); | ||
return this; | ||
callsFake(fn: (input: any) => any): AwsStub<TInput, TOutput> { | ||
this.send.callsFake(cmd => fn(cmd.input)); | ||
return this.clientStub; | ||
} | ||
private prepareStub(): SinonStub { | ||
return this.send.withArgs(this.getMatcher()); | ||
} | ||
private getMatcher(): SinonMatcher { | ||
return match.any | ||
.and(this.commandMatcher) | ||
.and(this.inputMatcher); | ||
} | ||
private resetMatchers() { | ||
this.commandMatcher = match.any; | ||
this.inputMatcher = match.any; | ||
} | ||
} | ||
/** | ||
* Type matching the Command instances accepted by the Client. | ||
*/ | ||
export type AwsCommand<TClient extends Client<any, any, any>> = Parameters<TClient['send']>[0]; | ||
type AwsCommand<Input extends ClientInput, Output extends ClientOutput, ClientInput extends object = any, ClientOutput extends MetadataBearer = any> = Command<ClientInput, Input, ClientOutput, Output, any>; | ||
type CommandResponse<TOutput> = Partial<TOutput> | PromiseLike<Partial<TOutput>>; | ||
/** | ||
* Type matching the Command type (class) accepted by the Client. | ||
*/ | ||
export type AwsCommandType<TClient extends Client<any, any, any>, TInput> = new (input: TInput) => MatchArguments<AwsCommand<TClient>>; | ||
/** | ||
* Based on solution from Sinon sources. | ||
*/ | ||
type MatchArguments<T> = { | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
[K in keyof T]: (T[K] extends object ? MatchArguments<T[K]> : never) | T[K]; | ||
}; | ||
export interface AwsError extends Partial<Error>, Partial<MetadataBearer> { | ||
@@ -203,0 +212,0 @@ Type?: string; |
@@ -1,5 +0,5 @@ | ||
import {Client, Command} from '@aws-sdk/types'; | ||
import {Client, Command, MetadataBearer} from '@aws-sdk/types'; | ||
import {SinonStub, stub} from 'sinon'; | ||
import {isSinonStub} from './sinon'; | ||
import {AwsClientStub} from './awsClientStub'; | ||
import {AwsClientStub, AwsStub} from './awsClientStub'; | ||
@@ -12,5 +12,5 @@ /** | ||
*/ | ||
export const mockClient = <TClient extends AwsClient>( | ||
client: TClient | Constructor<TClient>, | ||
): AwsClientStub<TClient> => { | ||
export const mockClient = <TInput extends object, TOutput extends MetadataBearer>( | ||
client: InstanceOrClassType<Client<TInput, TOutput, any>>, | ||
): AwsClientStub<Client<TInput, TOutput, any>> => { | ||
const instance = isClientInstance(client) ? client : client.prototype; | ||
@@ -23,10 +23,8 @@ | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore // TODO Resolve - see: https://stackoverflow.com/questions/56505560/could-be-instantiated-with-a-different-subtype-of-constraint-object-ts2322 | ||
const sendStub: SinonStub<[Command<any, any, any, any, any>], unknown> = stub(instance, 'send'); | ||
const sendStub: SinonStub<[Command<TInput, any, TOutput, any, any>], unknown> = stub(instance, 'send'); | ||
return new AwsClientStub<TClient>(sendStub); | ||
return new AwsStub<TInput, TOutput>(sendStub); | ||
}; | ||
type Constructor<T> = { | ||
type ClassType<T> = { | ||
new(...args: never[]): T; | ||
@@ -36,3 +34,3 @@ prototype: T; | ||
type AwsClient = Client<any, any, any>; | ||
type InstanceOrClassType<T> = T | ClassType<T>; | ||
@@ -42,3 +40,3 @@ /** | ||
*/ | ||
const isClientInstance = <TClient extends AwsClient>(obj: TClient | Constructor<TClient>): obj is TClient => | ||
const isClientInstance = <TClient extends Client<any, any, any>>(obj: InstanceOrClassType<TClient>): obj is TClient => | ||
(obj as TClient).send !== undefined; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
48836
705
346