This .NET library is intended to provide a simple in-memory drop-in replacement for the AWS SDK for SQS and SNS, primarily for testing (but can be used for local development too).
Why would you build this when LocalStack already exists, and is awesome?
One word: Speed 🏎️️⚡⚡
While LocalStack is relatively quick, nothing is a replacement for in-memory operations, it means you can run your tests faster, and you can run them in parallel without worrying about port conflicts.
Don't take our word for it, here are our tests for this project at the time of writing, ran against this library and LocalStack (to verify correctness):
Tip
The LocalStack tests above were ran with the default behaviour of xUnit, which is to not run tests in parallel when a test collection is used. You can speed up this sort of test suite by fighting against xUnit's defaults (see Meziantou.Xunit.ParallelTestFramework for example) and getting tests to run in parallel. LocalStack has feature where if you pass an access key that looks like an account id, it will use this account for any resources created, this can help isolate tests from each other allowing them to run in parallel.
Additionally, some tests rely on the passage of time, but now with .NET's TimeProvider
you can control time in your tests, and travel through time like it's 1985, Great Scott!
Creating a topic, a queue, subscribing the queue to the topic, and sending a message to the topic, then receiving the message from the queue.
using Amazon.SimpleNotificationService.Model;
using LocalSqsSnsMessaging;
var bus = new InMemoryAwsBus();
using var sqs = bus.CreateSqsClient();
using var sns = bus.CreateSnsClient();
// Create a queue and a topic
var queueUrl = (await sqs.CreateQueueAsync("test-queue")).QueueUrl;
var topicArn = (await sns.CreateTopicAsync("test-topic")).TopicArn;
var queueArn = (await sqs.GetQueueAttributesAsync(queueUrl, ["QueueArn"])).Attributes["QueueArn"];
// Subscribe the queue to the topic
await sns.SubscribeAsync(new SubscribeRequest(topicArn, "sqs", queueArn)
{
Attributes = new() { ["RawMessageDelivery"] = "true" }
});
// Send a message to the topic
await sns.PublishAsync(topicArn, "Hello, World!");
// Receive the message from the queue
var receiveMessageResponse = await sqs.ReceiveMessageAsync(queueUrl);
var message = receiveMessageResponse.Messages.Single();
Console.WriteLine(message.Body); // Hello, World!
Creating a queue, sending a message to the queue with a delay, advancing time, and receiving the message from the queue.
using Amazon.SQS.Model;
using LocalSqsSnsMessaging;
using Microsoft.Extensions.Time.Testing;
var timeProvider = new FakeTimeProvider(); // From `Microsoft.Extensions.TimeProvider.Testing` package
var bus = new InMemoryAwsBus { TimeProvider = timeProvider};
using var sqs = bus.CreateSqsClient();
using var sns = bus.CreateSnsClient();
// Create a queue
var queueUrl = (await sqs.CreateQueueAsync("test-queue")).QueueUrl;
// Send a message to the topic
await sqs.SendMessageAsync(new SendMessageRequest(queueUrl, "Hello, World!")
{
DelaySeconds = 30
});
// Receive the message from the queue
var firstReceiveMessageResponse = await sqs.ReceiveMessageAsync(queueUrl);
Console.WriteLine(firstReceiveMessageResponse.Messages.Count); // 0
// Advance time by 31 seconds
timeProvider.Advance(TimeSpan.FromSeconds(31));
// Receive the message from the queue
var secondReceiveMessageResponse = await sqs.ReceiveMessageAsync(queueUrl);
var message = secondReceiveMessageResponse.Messages.Single();
Console.WriteLine(message.Body); // Hello, World!
All actions in this library that depend on delays or timeouts use the TimeProvider
to control time, so you can also take advantage of this feature with features like visibility timeouts.