# Unit Testing AWS SDKs in .NET core

Automated unit testing is essential for production systems, you probably don't need to be told that! If you're programming in C# you're probably familiar with [Dependency Injection](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0) and the Inversion of Control (IoC) pattern.

Sometimes you come across third-party libraries that don't provide interfaces, such as AWS SDKs. How would you unit test a part of your system that relied on these libraries? Easy! By combining concrete dependency and a little re-write of your system under test.

Let's look at the AWS IoT Core SDKs. [There are AWS SDKs for .NET](https://aws.amazon.com/sdk-for-net/). The documentation will feel light-on if you don't have AWS IoT experience already, and sometimes there aren't even C# examples in the [developer guides](https://docs.aws.amazon.com/iot/latest/developerguide/iot-sdks.html).

# Dependencies

To run these examples I'm using a .NET core 3.1 project with the following package:
- AWSSDK.IoT 3.7.2.19

I'm using a separate class-library project with the following packages for unit-testing:
- Microsoft.NET.Test.Sdk 16.6.1
- Moq 4.14.5 
- nunit 3.12.0 
- NUnit3TestAdapter 3.17.0 

# Using AWS Iot Core SDKs

AWS provides various [IoT Core endpoints](https://docs.aws.amazon.com/general/latest/gr/iot-core.html) depending on what you want to do. For these examples I will be using the *Control Plane*. The general steps for using these AWS SDKs are as follows:

1. instantiate a client
1. prepare the request for one of the client's supported methods
1. call the method on the client
1. interpret the response

For example, to get information on an IoT "thing", you might do the following:

```
// my function for querying things
public async Task<string> GetThingType(string deviceKey, CancellationToken cancellationToken)
{
	using var client = new AmazonIoTClient(); // STEP 1
	var request = new DescribeThingRequest {ThingName = deviceKey}; // STEP 2
	DescribeThingResponse response = await client.DescribeThingAsync(request, cancellationToken); // STEP 3
	if (response.HttpStatusCode == HttpStatusCode.OK)
	{
		return response.ThingTypeName; // STEP 4
	}
	// log error, throw exception, etc.
	return "";
}
```

# The Problem

Now how do you unit test this? Even if you put `GetThingType()` behind an interface, you can't really mock out the `AmazonIoTClient`. Your test would attempt to connect to AWS IoT Core. The pointer to the fact that something might be wrong with this design ("code smell") is the `new` keyword. Don't get me wrong, there's nothing wrong with using `new`, but in this case, AmazonIoTClient is an infrastructure style of service/dependency, not a POCO, and dependencies (using IoC) should be provided to our system, not instantiated by our system.

# The Solution

So let's redesign this by setting up AmazonIoTClient as a dependency that can be injected. In Startup.cs:
```
public void ConfigureServices(IServiceCollection services)
{
   // ...
   services.AddSingleton<AmazonIoTClient>();
   services.AddScoped<IMyAwsIotService, MyAwsIotService>();
}
```
Now setup your service class for constructor injection:
```
public class MyAwsIotService : IMyAwsIotService
{
	private readonly AmazonIoTClient _iotClient;

	public MyAwsIotService(AmazonIoTClient amazonIoTClient)
	{
		_iotClient = amazonIoTClient
	}

	async Task<string> IMyAwsIotService.GetThingType(string deviceKey, CancellationToken cancellationToken)
	{
		// ...
	}
}
```

And finally, in the `GetThingType` function, remove `using var client = new AmazonIoTClient();` (Step 1) and replace `client` with our injected `_iotClient`. Your final function will look something like this:
```
// my function for querying things
async Task<string> IMyAwsIotService.GetThingType(string deviceKey, CancellationToken cancellationToken)
{
	var request = new DescribeThingRequest {ThingName = deviceKey}; // STEP 2
	DescribeThingResponse response = await _iotClient.DescribeThingAsync(request, cancellationToken); // STEP 3
	if (response.HttpStatusCode == HttpStatusCode.OK)
	{
		return response.ThingTypeName; // STEP 4
	}
	// log error, throw exception, etc.
	return "";
}
```

# Unit Testing

Now comes the cool bit. In another class-library project, install the Mock and nunit dependencies above. Create a class for testing (I call it `MyAwsIotServiceTests`):
```
[TestFixture]
public class MyAwsIotServiceTests
{
	// System under tests
	private IMyAwsIotService _service;
	// services
	private Mock<AmazonIoTClient> _iotClient;

	[SetUp]
	public void Setup()
	{
		_iotClient = new Mock<AmazonIoTClient>();
		AWSConfigs.AWSRegion = "ap-southeast-2"; // required for unit tests on build server which doesn't have AWS credentials/profile

		_service = new MyAwsIotService(_iotClient.Object);
	}

	[Test]
	public async Task HandlerExecutes()
	{
		// Arrange
		var token = new CancellationToken();

		_iotClient.Setup(s => s.DescribeThingAsync(It.IsAny<DescribeThingRequest>(), It.IsAny<CancellationToken>()))
			.ReturnsAsync(new DescribeThingResponse
			{
				ThingTypeName = "someThingType",
				HttpStatusCode = HttpStatusCode.OK
			});

		// Act
		string type = await _service.GetThingType("abcd1234", token);

		// Assert
		Assert.AreEqual("someThingType", type);
	}
}
```

This will test your service, and ensure that the `ThingTypeName` is returned. In practice you would have lots of tests for various HttpStatusCode responses and other things, such as when an exception is thrown. In this case you may want to ensure your service catches the error and returns the empty string:
```
[Test]
public async Task NoSuchThing()
{
	// Arrange
	var token = new CancellationToken();

	_iotClient.Setup(s => s.DescribeThingAsync(It.IsAny<DescribeThingRequest>(), It.IsAny<CancellationToken>()))
		.Throws(new Amazon.IoT.Model.ResourceNotFoundException("uh-oh"));

	// Act
	string type = await _service.GetThingType("abcd1234", token);

	// Assert
	Assert.AreEqual("", type);
}
```

# Conclusion
I hope this has been helpful in both connecting to IoT Core (as there are few examples for .NET core) and for mocking your services that may call AWS SDKs.

Let me know what SDKs you use and how you might test differently!
