Class FakeDynamoDb
An in-memory DynamoDB that speaks the wire protocol behind an HttpMessageHandler, for functional tests: a test sets up tables and items, runs the code under test through a real client, and reads the state back. No network, no container, no queued answers.
public sealed class FakeDynamoDb
- Inheritance
-
FakeDynamoDb
- Inherited Members
Examples
var fake = new FakeDynamoDb();
fake.CreateTable<Customer>("Shop");
using var client = fake.CreateClient();
await client.Table<Customer>("Shop").Put(customer).SendAsync(ct); // the code under test
var stored = fake.Get<Customer>("Shop", StubResponse.KeyOf(Customer.Keys.Of("alice")));
Remarks
Clients. CreateClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>?) gives a client over the fake, CreateStreamsClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>?) one of DynamoDB Streams, and StubClient.Create(fake.CreateHandler()) is the same: CreateHandler() returns a
StubHandler whose responder is Answer(RecordedRequest), so Requests records what the client sent, and a response queued with
Enqueue(StubResponse) answers the next request instead of the fake, which never sees it: a queued throttle changes no state, as a throttle of DynamoDB does not. Every handler
of one fake shares its state. A test of a write whose response is lost wraps the fake in a responder of its own that answers, and then returns an error in place of the answer.
What it answers. CreateTable, DescribeTable, UpdateTable, DeleteTable, ListTables, DescribeTimeToLive, UpdateTimeToLive,
DescribeLimits, GetItem, PutItem, UpdateItem, DeleteItem, Query, Scan, BatchGetItem, BatchWriteItem, TransactGetItems and
TransactWriteItems, with condition, update, key condition, filter and projection expressions, ReturnValues, ReturnConsumedCapacity and a transaction token that holds for
10 minutes on the clock, and of DynamoDB Streams ListStreams, DescribeStream, GetShardIterator and GetRecords: a table with a stream records every change
of an item in a shard, in the order of the writes, the records of a request together and none of a request that failed. A table and its indexes are ACTIVE in the answer that creates them, an index holds the items
of its table at once and follows every write at once, and every read sees the last write. Keys are checked against the schema with the messages of DynamoDB, as are the type of a key of an index,
empty keys, the sizes of keys and the item limit of 400 KB, and numbers keep their 38 digits and come back in canonical form, 1.50 as 1.5.
What it does not do. An operation it does not answer, such as PartiQL, is HTTP 400 with UnknownOperationException. A member it does not implement inside an
operation it answers, such as tags or a legacy parameter, is HTTP 400 with the code UnsupportedByFakeException, which names the member: the fake never ignores a member. Nothing throttles, nothing is eventually consistent,
no transaction conflicts, a batch leaves nothing unprocessed, a stream has one shard until SplitStreamShards(string, int, TimeSpan) gives it children, and nothing expires by itself.
Time. The clock given to the constructor dates the tables and the stream records, expires shard iterators after 15 minutes and trims records after 24 hours, and ExpireItems() deletes what has expired by it. One fake per test, made with new: it holds no static state and nothing to dispose. It is thread-safe, and each request runs under one lock from start to end.
The direct API. CreateTable(CreateTableRequest), Put(string, Item), Get(string, Item), Delete(string, Item), Items(string) and the rest set up and read state without a client, and go through the same checks as a request: a key that does not match the schema throws the ValidationException a client would get.
Constructors
FakeDynamoDb(TimeProvider?)
Initializes a new instance of the FakeDynamoDb class, without tables.
public FakeDynamoDb(TimeProvider? timeProvider = null)
Parameters
timeProviderTimeProviderThe clock of creation times, of time to live, of the 10 minutes a client request token of a transaction holds and of the streams, System if null.
Properties
TableNames
Gets the names of the tables, in ordinal order, as ListTables gives them.
public IReadOnlyList<string> TableNames { get; }
Property Value
Methods
Answer(RecordedRequest)
Answers a request a StubHandler received: the operation from X-Amz-Target, and the body as sent.
public StubResponse Answer(RecordedRequest request)
Parameters
requestRecordedRequestThe request.
Returns
- StubResponse
The answer, with
x-amzn-RequestId. It fits any operation, because it is the answer to the operation of the request.
Exceptions
- ArgumentNullException
requestis null.
Answer(string, string)
Answers a request given as text, which is what a server in front of the fake or a raw client calls.
public StubResponse Answer(string operation, string requestJson)
Parameters
operationstringThe operation:
GetItem, or the whole target,DynamoDB_20120810.GetItem.requestJsonstringThe body.
Returns
- StubResponse
The answer.
Exceptions
- ArgumentNullException
An argument is null.
CreateClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>?)
Creates a client over a new handler of this fake, as Create(HttpMessageHandler, Func<DynamoDbClientOptions, DynamoDbClientOptions>?) makes it: us-east-1, static fake credentials, one attempt per call.
public DynamoDbClient CreateClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>? configure = null)
Parameters
configureFunc<DynamoDbClientOptions, DynamoDbClientOptions>Adjusts the options with a
withexpression, or null to keep them.
Returns
- DynamoDbClient
The client.
Exceptions
- ArgumentNullException
configurereturns null.
CreateHandler()
Creates a handler that answers every request from this fake, for Create(HttpMessageHandler, Func<DynamoDbClientOptions, DynamoDbClientOptions>?) or for HttpHandler.
public StubHandler CreateHandler()
Returns
- StubHandler
A new StubHandler whose responder is Answer(RecordedRequest). It holds nothing that needs disposing.
CreateStreamsClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>?)
Creates a client of DynamoDB Streams over a new handler of this fake, as CreateStreams(HttpMessageHandler, Func<DynamoDbClientOptions, DynamoDbClientOptions>?) makes it: it reads the streams of the tables of this fake, and ReadShardAsync(ReadShardRequest, CancellationToken) and ReadStreamAsync(ReadStreamRequest, CancellationToken) work over it as over DynamoDB.
public DynamoDbStreamsClient CreateStreamsClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>? configure = null)
Parameters
configureFunc<DynamoDbClientOptions, DynamoDbClientOptions>Adjusts the options with a
withexpression, or null to keep them.
Returns
- DynamoDbStreamsClient
The client.
Remarks
Its TimeProvider is the clock of this fake, so that what the client waits for the stream, EmptyPageDelay and
the looks for the children of a shard, passes as the fake's iterators expire, its records age and its late children are listed: a test that moves the clock moves both.
configure can set another. CreateClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>?) keeps the system clock, because its client waits only for itself, between the runs of
RetryOnConflictAsync, which a test should not have to move a clock for.
Those waits also end at the next change of a stream of this fake: a record, a split, a stream enabled or disabled, a Reset(). So a reader that found nothing asks again as soon as a write of the test is there, and a reading ends as soon as the stream is disabled, without moving any clock: a test on the system clock reads a write in milliseconds and not after EmptyPageDelay. The reader sends the same requests as against DynamoDB, an empty page, a wait and the next page, and only the wait is shorter. A late child listed by the clock alone, see SplitStreamShards(string, int, TimeSpan), is found after the whole wait, as a retry after a failure, see FailNext(string, StubResponse, int, Func<RecordedRequest, bool>?), is sent after its whole backoff.
Exceptions
- ArgumentNullException
configurereturns null.
CreateTable(CreateTableRequest)
Creates a table, as a CreateTable request does, with the same checks. It is ACTIVE at once.
public TableDescription CreateTable(CreateTableRequest request)
Parameters
requestCreateTableRequestThe request, as the request API takes it.
Returns
- TableDescription
The description, as
DescribeTablegives it.
Exceptions
- ArgumentNullException
requestis null.- ResourceInUseException
A table of that name exists.
- ValidationException
DynamoDB would reject the request.
- DynamoDbException
The request asks for what the fake does not implement, such as tags: ErrorCode is
UnsupportedByFakeException.
CreateTable(string, string, string?)
Creates a table without a model, on demand, whose keys are strings: the table of client.Table(name, partitionKey, sortKey), such as the table
of a LockTable of DynaSharp.Coordination. A table with keys of other types, indexes or a stream takes CreateTable(CreateTableRequest).
public TableDescription CreateTable(string tableName, string partitionKey, string? sortKey = null)
Parameters
tableNamestringThe table.
partitionKeystringThe attribute name of the partition key, a string.
sortKeystringThe attribute name of the sort key, a string, or null for a table without one.
Returns
- TableDescription
The description.
Exceptions
- ArgumentNullException
tableNameorpartitionKeyis null.- ResourceInUseException
A table of that name exists.
- ValidationException
DynamoDB would reject the request, such as for an empty name or a sort key named as the partition key.
CreateTable<T>()
Creates the table an entity declares, as CreateTable<T>(string) does under that name: Table, or the default of its assembly.
public TableDescription CreateTable<T>() where T : IDynamoDbEntity<T>
Returns
- TableDescription
The description.
Type Parameters
TThe entity, which declares its table.
Remarks
The name as declared. The fake knows no TableNaming, so a client of the fake whose options set one opens another table: such a test passes the name it expects to CreateTable<T>(string).
Exceptions
- InvalidOperationException
Tdeclares no table.- ResourceInUseException
A table of that name exists.
CreateTable<T>(string)
Creates the table an entity lives in, as client.Table<T>(tableName).CreateTable() would: its keys with their types and every index it declares, on demand, and time to live ENABLED on the
attribute of its TimeToLiveAttribute, so ExpireItems() sweeps it. A table of several entities takes CreateTable(CreateTableRequest) with the request of
CreateTable().Including<TOther>().ToRequest(), which holds no time to live, and then UpdateTimeToLive through a client of the fake for one.
public TableDescription CreateTable<T>(string tableName) where T : IDynamoDbEntity<T>
Parameters
tableNamestringThe table.
Returns
- TableDescription
The description.
Type Parameters
TThe entity.
Exceptions
- ArgumentNullException
tableNameis null.- ResourceInUseException
A table of that name exists.
Delete(string, Item)
Deletes an item by its key, as a DeleteItem without a condition does.
public bool Delete(string tableName, Item key)
Parameters
Returns
Exceptions
- ArgumentNullException
An argument is null.
- ResourceNotFoundException
There is no such table.
- ValidationException
The key does not match the schema of the table.
DeleteTable(string)
Deletes a table with its items. The name is free at once.
public void DeleteTable(string tableName)
Parameters
tableNamestringThe table.
Exceptions
- ArgumentNullException
tableNameis null.- ResourceNotFoundException
There is no such table.
- ValidationException
The table is protected against deletion.
DescribeTable(string)
Describes a table, as DescribeTable does, with its item count and size as they are now.
public TableDescription DescribeTable(string tableName)
Parameters
tableNamestringThe table.
Returns
- TableDescription
The description.
Exceptions
- ArgumentNullException
tableNameis null.- ResourceNotFoundException
There is no such table.
DisableStream(string)
Disables the stream of a table, as an UpdateTable with StreamSpecification StreamEnabled: false does: its open shards close, so a reader of a shard
reaches its end, DescribeStream says DISABLED, and ReadStreamAsync ends once it has read every shard. The stream stays readable for 24 hours on the
clock of the fake, and the table keeps naming it in LatestStreamArn.
public TableDescription DisableStream(string tableName)
Parameters
tableNamestringThe table, whose stream is enabled.
Returns
- TableDescription
The description, as
DescribeTablegives it, withoutStreamSpecification.
Exceptions
- ArgumentNullException
tableNameis null.- ResourceNotFoundException
There is no such table.
- ValidationException
The table has no enabled stream.
EnableStream(string, StreamViewType)
Enables a stream on a table, as an UpdateTable with StreamSpecification StreamEnabled: true does: a new stream with one open shard, a new
LatestStreamArn and a new LatestStreamLabel, also on a table whose stream was enabled before. The writes from then on are its records.
public TableDescription EnableStream(string tableName, StreamViewType viewType)
Parameters
tableNamestringThe table, which has no enabled stream.
viewTypeStreamViewTypeWhat a record holds of the item.
Returns
- TableDescription
The description, as
DescribeTablegives it, with the new stream inLatestStreamArn.
Exceptions
- ArgumentNullException
tableNameis null.- ArgumentOutOfRangeException
viewTypeis Unspecified or undefined.- ResourceNotFoundException
There is no such table.
- ValidationException
The table has an enabled stream: another view type takes DisableStream(string) first, as on DynamoDB.
ExpireItems()
Deletes every item that has expired: in each table with time to live ENABLED, an item whose attribute of time to live is a number of epoch seconds at or before the clock of the fake. An expiry more than five years before the clock is left, as DynamoDB leaves it.
Nothing expires by itself; this is the background sweep of DynamoDB, run when the test says. A table with a stream records each delete as a REMOVE whose userIdentity is
the service, dynamodb.amazonaws.com of type Service, as DynamoDB records it.
public int ExpireItems()
Returns
- int
The number of items deleted.
FailNext(string, StubResponse, int, Func<RecordedRequest, bool>?)
Makes the next requests of an operation fail: fake.FailNext("GetRecords", StubResponse.DynamoDbError("ThrottlingException", "Rate exceeded")). The request gets
failure and the fake never sees it, as DynamoDB never sees a request it throttles, so a failed write writes nothing; the requests after it are
answered by the fake as before.
public void FailNext(string operation, StubResponse failure, int times = 1, Func<RecordedRequest, bool>? when = null)
Parameters
operationstringThe operation,
GetRecords, or its whole target,DynamoDBStreams_20120810.GetRecords.failureStubResponseThe answer, such as DynamoDbError(string, string, HttpStatusCode, string?). A response of another operation fails the request, as a queued one does on a StubHandler.
timesintHow many requests get it, one by default.
whenFunc<RecordedRequest, bool>Which requests of the operation count, such as those of one table,
request => request.BodyJson.GetProperty("TableName").GetString() == "Orders", or null for all. It runs under the lock of the fake, so it must not call the fake.
Remarks
It holds for every handler of this fake, those made before the call too, so for the clients of CreateClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>?) and CreateStreamsClient(Func<DynamoDbClientOptions, DynamoDbClientOptions>?) alike, and for
Answer(RecordedRequest) and Answer(string, string). A response queued with Enqueue(StubResponse) on a handler still comes first: the handler
asks the fake only when its queue is empty. Requests of several threads, such as the shard readers of ReadStreamAsync, take the failures one at a time under the lock of the fake,
so each failure is taken once.
Failures are taken in the order planned: a request takes the first one of its operation that when accepts. A client repeats a throttling error or a server error
as it does on DynamoDB, so a failure planned for more requests than the client has attempts surfaces, and ReadStreamAsync reads a shard on after it. Reset()
drops the failures not taken, and ThrowIfFailuresLeft() tells a test that the code sent fewer requests than it planned for. The direct API, such as
Put(string, Item), is no request and takes none.
Exceptions
- ArgumentNullException
operationorfailureis null.- ArgumentException
operationis empty.- ArgumentOutOfRangeException
timesis less than one.
Get(string, Item)
Gets an item by its key, as a GetItem does.
public Item? Get(string tableName, Item key)
Parameters
Returns
Exceptions
- ArgumentNullException
An argument is null.
- ResourceNotFoundException
There is no such table.
- ValidationException
The key does not match the schema of the table.
Get<T>(Item)
Gets an item by its key from the table the entity declares, as Get<T>(string, Item) does.
public T? Get<T>(Item key) where T : IDynamoDbEntity<T>
Parameters
keyItemThe key attributes, such as
StubResponse.KeyOf(Customer.Keys.Of("alice")).
Returns
- T
The entity, or the default of
Tif there is no item: null for a class.
Type Parameters
TThe entity, which declares its table.
Exceptions
- ArgumentNullException
keyis null.- InvalidOperationException
Tdeclares no table.- ResourceNotFoundException
There is no such table.
- ValidationException
The key does not match the schema of the table.
- DynamoDbMappingException
The item is not one the entity can read, also when it is of another entity.
Get<T>(string, Item)
Gets an item by its key and reads it as an entity.
public T? Get<T>(string tableName, Item key) where T : IDynamoDbDocument<T>
Parameters
tableNamestringThe table.
keyItemThe key attributes, such as
StubResponse.KeyOf(Customer.Keys.Of("alice")).
Returns
- T
The entity, or the default of
Tif there is no item: null for a class.
Type Parameters
TA generated entity or document.
Remarks
A key names one item, so an item of another entity on that key is a mistake of the test and throws, as Get(key).ItemAsync() of a client throws for it. Unlike Items<T>(string), it
does not skip it.
Exceptions
- ArgumentNullException
An argument is null.
- ResourceNotFoundException
There is no such table.
- ValidationException
The key does not match the schema of the table.
- DynamoDbMappingException
The item is not one the entity can read, also when it is of another entity.
Items(string)
Gets every item of a table, in key order: partitions in the order of their keys, then sort keys in the order of DynamoDB. A test should not depend on the order of partitions, as on DynamoDB.
public IReadOnlyList<Item> Items(string tableName)
Parameters
tableNamestringThe table.
Returns
- IReadOnlyList<Item>
Copies of the items, a snapshot.
Exceptions
- ArgumentNullException
tableNameis null.- ResourceNotFoundException
There is no such table.
Items<T>()
Gets the items of the entity from the table it declares, as Items<T>(string) does.
public IReadOnlyList<T> Items<T>() where T : IDynamoDbEntity<T>
Returns
- IReadOnlyList<T>
The entities, a snapshot.
Type Parameters
TThe entity, which declares its table.
Exceptions
- InvalidOperationException
Tdeclares no table.- ResourceNotFoundException
There is no such table.
- DynamoDbMappingException
An item of the entity, or with a type without a discriminator any item, is not one the type can read.
Items<T>(string)
Gets the items of a table that are of an entity, in the order of Items(string), read as entities. On a table of several entities it gives those of T and skips
the others, as a typed Scan() does with its filter on the discriminator.
public IReadOnlyList<T> Items<T>(string tableName) where T : IDynamoDbDocument<T>
Parameters
tableNamestringThe table.
Returns
- IReadOnlyList<T>
The entities, a snapshot.
Type Parameters
TA generated entity or document.
Remarks
For an entity with a discriminator, an item is of it when its discriminator attribute, _type or the one the entity names, is a string equal to the discriminator, which is what
Is<T>() tests. Every other item is skipped: one of another entity, and one without the attribute, which a scan of the client does not return either. An item that is of the entity and
that it cannot read throws, because that is a bug of the model or of a writer.
For an entity without a discriminator, with [Discriminator(OptOut = true)], and for a document, nothing tells the items apart, so every item is read, and one that the type cannot read throws.
Exceptions
- ArgumentNullException
tableNameis null.- ResourceNotFoundException
There is no such table.
- DynamoDbMappingException
An item of the entity, or with a type without a discriminator any item, is not one the type can read.
Put(string, Item)
Puts an item, as a PutItem without a condition does: it replaces the item with its key, after the checks of the keys, the keys of the indexes and the size, and the indexes follow.
public void Put(string tableName, Item item)
Parameters
Exceptions
- ArgumentNullException
An argument is null.
- ResourceNotFoundException
There is no such table.
- ValidationException
DynamoDB would reject the item: a key attribute is missing or of another type, a number is out of range, the item is larger than 400 KB.
Put<T>(string, T)
Puts an entity, as a PutItem of From<T>(T) does: with the keys, the discriminator and the version the entity writes. A version is stored as the entity holds it, not
raised as a Put command raises it.
public void Put<T>(string tableName, T entity) where T : IDynamoDbDocument<T>
Parameters
tableNamestringThe table.
entityTThe entity.
Type Parameters
TA generated entity or document.
Exceptions
- ArgumentNullException
An argument is null.
- ResourceNotFoundException
There is no such table.
- ValidationException
DynamoDB would reject the item.
Put<T>(T)
Puts an entity into the table it declares, as Put<T>(string, T) does.
public void Put<T>(T entity) where T : IDynamoDbEntity<T>
Parameters
entityTThe entity.
Type Parameters
TThe entity, which declares its table.
Exceptions
- ArgumentNullException
entityis null.- InvalidOperationException
Tdeclares no table.- ResourceNotFoundException
There is no such table.
- ValidationException
DynamoDB would reject the item.
Reset()
Drops every table with its items and streams, forgets every client request token, and drops the failures planned with FailNext(string, StubResponse, int, Func<RecordedRequest, bool>?) that no request took. The handlers and clients of this fake stay valid and see no tables. A stream enabled
afterwards gets another ARN than the streams before, also on the same clock, so a shard iterator from before is ResourceNotFoundException.
public void Reset()
SplitStreamShards(string, int, TimeSpan)
Ends every open shard of the stream of a table and opens its children, as DynamoDB does on its own: one child when it rolls a shard over, which it does every few hours, two
when it splits a shard with its partition. The children take the records of the shard from then on, each the items of its share of the partition keys, and name it in
ParentShardId. The ended shard gets its EndingSequenceNumber, and a reader of it reaches its end.
public void SplitStreamShards(string tableName, int children = 2, TimeSpan secondChildListedAfter = default)
Parameters
tableNamestringThe table, whose stream is enabled.
childrenintHow many children each open shard gets: 1 or 2, the most a shard of DynamoDB has.
secondChildListedAfterTimeSpanHow long after the split the second child is listed; zero, the default, lists it at once. It applies only to two children.
Remarks
DynamoDB can list a child in DescribeStream a moment after its parent ended. secondChildListedAfter plays that for the second of two children: it takes
records at once and is listed, also by a ShardFilter of CHILD_SHARDS, only once the clock of the fake has moved on by that much.
Exceptions
- ArgumentNullException
tableNameis null.- ArgumentOutOfRangeException
childrenis not 1 or 2, orsecondChildListedAfteris negative.- ResourceNotFoundException
There is no such table.
- ValidationException
The table has no enabled stream.
ThrowIfFailuresLeft()
Throws when a failure planned with FailNext(string, StubResponse, int, Func<RecordedRequest, bool>?) was not taken, naming the operation and how many are left.
public void ThrowIfFailuresLeft()
Exceptions
- InvalidOperationException
A failure is left: the code under test sent fewer requests than the test planned failures for.