Alexa.Tip – Build Unit Tested Skills in .NET

In this Alexa.Tip series, we explore some little bits of code that can make your life easier in developing Alexa Skills in many languages including C# + .NET, node.jS + TypeScript, Kotlin, etc. We look at concepts, design patterns, and implementations that developers might not be aware of, and how they can be applied to voice application development, best practices, and more!

In this post, we explore some more best practices in developing Alexa Skills in C# whether you are using an ASP.NET Core API or an AWS Lambda. This post will expand our previous posts about building better abstractions for handling Alexa responses and demonstrate how we can now properly Unit test these handlers as well as any other separated bits of logic that the Handler implementations consume.

Check out all the raw source code for this post, and more here: https://github.com/SuavePirate/Alexa.Tips.Net

Prerequisite

If you haven’t read up on how to use the Handler Registration Pattern, take a look at my earlier post here: Alexa.Tip – Using Handler Registration Pattern in .NET

The short version is that we use this pattern of registering IHandler implementations to handle different types of requests that our skill receives, regardless of whether we are using AWS Lambdas or ASP.NET Core APIs.

Unit Testing Handlers

Since we now have nice atomized units (Handlers) built for each of our RequestTypes and Intents, they are SCREAMING to be unit tested. So, we can easily test them against different scenarios given our positive and negative cases.

Take for example, the SimpleLaunchHandler that is responsible only for LaunchRequest, and whose HandleAsync() implementation returns a static response. The two main cases we want to test against is the HandleAsync() returning properly for a proper LaunchRequest, and that the CanHandle() implementation returns false for non LaunchRequest skill requests.

In these samples, I’m using xUnit for my Unit tests, but the same concept is applicable with any other Unit testing framework (and really any other language).

In each case, we want to separate our 3 A’s of testing, “Arrange”, “Act”, and “Assert”. For these basic examples, the three steps are pretty clear.

  1. Arrange the Testable SkillRequest object
  2. Act on the subject’s method we are testing by sending it the SkillRequest
  3. Assert our final response

Let’s first test the CanHandle() against the postive case, meaning we want to send it data we want to be successful:

public class SimpleLaunchHandlerTests
{
    private readonly SimpleLaunchHandler _subject;
    public SimpleLaunchHandlerTests()
    {
        _subject = new SimpleLaunchHandler();
    }


    [Fact]
    public void SimpleHandler_CanHandleLaunchRequest()
    {
        // arrange
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new LaunchRequest
            {
                Type = "LaunchRequest"
            }
        };

        // act
        var canHandle = _subject.CanHandle(request);

        // assert
        Assert.True(canHandle);
    }

}

Now let’s perform the opposite by sending it a request of a different type:

 [Fact]
public void SimpleHandler_CanNotHandleIntentRequest()
{
    // arrange
    var request = new SkillRequest()
    {
        Version = "1.0",
        Request = new IntentRequest
        {
            Type = "IntentRequest"
        }
    };

    // act
    var canHandle = _subject.CanHandle(request);

    // assert
    Assert.False(canHandle);
}

Now, we can look at the positive example of passing the LaunchRequest into the HandleAsync method:

[Fact]
public async Task SimpleHandler_ReturnsResponse()
{
    // arrange
    var request = new SkillRequest()
    {
        Version = "1.0",
        Request = new LaunchRequest
        {
            Type = "LaunchRequest"
        }
    };

    // act
    var response = await _subject.HandleAsync(request);

    // assert
    Assert.NotNull((response.Response.OutputSpeech as PlainTextOutputSpeech)?.Text);
}

Now that we have our LaunchRequest tested, let’s take a look at a simple static IntentRequest set of tests:

DogFactHandlerTests.cs

public class DogFactHandlerTests : IClassFixture<DogFactHandler>
{
    private readonly DogFactHandler _subject = new DogFactHandler();


    [Fact]
    public async Task DogFactHandler_ReturnsResponse()
    {
        // arrange
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "DogFactIntent"
                }
            }
        };

        // act
        var response = await _subject.HandleAsync(request);

        // assert
        Assert.NotNull((response.Response.OutputSpeech as PlainTextOutputSpeech)?.Text);
    }

    [Fact]
    public void DogFactHandler_CanHandleIntentRequest()
    {
        // arrange
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "DogFactIntent"
                }
            }
        };
        // act
        var canHandle = _subject.CanHandle(request);

        // assert
        Assert.True(canHandle);
    }
    [Fact]
    public void DogFactHandler_CanNotHandleLaunchRequest()
    {
        // arrange
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new LaunchRequest
            {
                Type = "LaunchRequest"
            }
        };


        // act
        var canHandle = _subject.CanHandle(request);

        // assert
        Assert.False(canHandle);
    }
    [Fact]
    public void DogFactHandler_CanNotHandleOtherIntents()
    {
        // arrange
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "AMAZON.HelpIntent"
                }
            }
        };


        // act
        var canHandle = _subject.CanHandle(request);

        // assert
        Assert.False(canHandle);
    }
}

Okay, cool, but what about stuff that isn’t static? What if I’m getting my data from somewhere else and want to test those cases? Let’s bring Moq into the picture!

Let’s take this handler for example:

SampleFactHandler.cs

public class SampleFactHandler : GenericHandler, IHandler
{
    private readonly SampleMessageDbContext _context;
    public SampleFactHandler(SampleMessageDbContext context)
    {
        _context = context;
    }
    public override string IntentName => "SampleMessageIntent";

    public override Type RequestType => typeof(IntentRequest);

    public override async Task<SkillResponse> HandleAsync(SkillRequest request)
    {
        // just grab one as an example
        var message = await _context.SampleMessages.FirstOrDefaultAsync();
        return ResponseBuilder.Tell(message?.Content ?? "I don't have any messages for you.");
    }
}

So this Handler uses an Entity Framework Core DbContext with a table of SampleMessage objects to get the content. In this sample, we just grab the first one, but you could imagine some more complex data-logic to pull out the proper message.

To unit test this, we can pass in different “Mock” implementations of the SampleMessageDbContext. For this, I usually use Moq, but you can also pass in a full implementation that you’ve built yourself.

Here are some samples of testing this with different Moq’d contexts to test a few scenarios. Note: we still want to test against the CanHandle and HandleAsync as well, but now we have two different cases to test for such as if there are no messages in the db, and when there is one.

SampleFactHandlerTests.cs

public class SampleFactHandlerTests
{
    [Fact]
    public async Task SampleFactHandler_ReturnResponseWithData()
    {
        // arrange
        var context = new Mock<SampleMessageDbContext>();
        context.Setup(d => d.SampleMessages.FirstOrDefaultAsync(CancellationToken.None)).Returns(Task.FromResult(new SampleMessage
        {
            Id = Guid.NewGuid().ToString(),
            Content = "This is a mocked response message"
        }));
        var subject = new SampleFactHandler(context.Object);
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "SampleMessageIntent"
                }
            }
        };

        // act
        var response = await subject.HandleAsync(request);

        // assert
        Assert.NotNull((response.Response.OutputSpeech as PlainTextOutputSpeech)?.Text);
    }
    [Fact]
    public async Task SampleFactHandler_ReturnFallbackResponseWithNoData()
    {
        // arrange
        var context = new Mock<SampleMessageDbContext>();
        context.Setup(d => d.SampleMessages.FirstOrDefaultAsync(CancellationToken.None)).Returns(Task.FromResult<SampleMessage>(null));
        var subject = new SampleFactHandler(context.Object);
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "SampleMessageIntent"
                }
            }
        };

        // act
        var response = await subject.HandleAsync(request);

        // assert
        Assert.True((response.Response.OutputSpeech as PlainTextOutputSpeech)?.Text == "I don't have any messages for you.");
    }
}

We can also, and probably should also add some CanHandle and non-assinable Intent types to test, but for the sake of not making you read the same thing over and over again, these are the two that matters.

Unit Testing Controllers

Personally, I don’t typically unit test my Controllers, but that’s because I have some general rules in place to not change them one they are implemented. However, when working on a team, it may be easier and more sustainable to implement a few test on the controller to guarantee that changes made to it don’t affect the currently working implementation.

Some basic positive/negative test might look like this:

SimpleAlexaControllerTests.cs

public class SimpleAlexaControllerTests
{
    [Fact]
    public async Task AlexaController_ResponseWithHandler() 
    {
        // arrange
        var subject = new SimpleAlexaController(new List<IHandler>{ new DogFactHandler() });
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "DogFactIntent"
                }
            }
        };
        // act
        var response = _subject.HandleRequest(request);

        // assert
        Assert.NotNull((response.Response.OutputSpeech as PlainTextOutputSpeech)?.Text);
    }

    [Fact]
    public async Task AlexaController_ResponseWithoutHandler() 
    {
        // arrange
        var subject = new SimpleAlexaController(new List<IHandler>{ new SimpleLaunchHandler() }); // no DogFactHandler
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "DogFactIntent"
                }
            }
        };

        // act
        var response = _subject.HandleRequest(request);

        // assert
        Assert.Null(response);
    }
}

Unit Testing Lambda Functions

Just like testing the Controller, if you are using AWS Lambda rather than ASP.NET Core, you can create unit tests against your Function endpoint in order to get some coverage. That could look simple like something here:

FunctionHandlerTests.cs

public class FunctionHandlerTests
{

    [Fact]
    public async Task LambdaFunction_ResponseWithHandler() 
    {
        // arrange
        var subject = new SimpleAlexaHandler();
        subject.Handlers = new List<IHandler>{ new DogFactIntent() });
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "DogFactIntent"
                }
            }
        };
        // act
        var response = _subject.HandleRequest(request, null);

        // assert
        Assert.NotNull((response.Response.OutputSpeech as PlainTextOutputSpeech)?.Text);
    }

    [Fact]
    public async Task LambdaFunction_ResponseWithoutHandler() 
    {
        // arrange
        var subject = new SimpleAlexaHandler();
        subject.Handlers = new List<IHandler>{ new SimpleLaunchHandler() }); // no DogFactIntent handler
        var request = new SkillRequest()
        {
            Version = "1.0",
            Request = new IntentRequest
            {
                Type = "IntentRequest",
                Intent = new Intent
                {
                    Name = "DogFactIntent"
                }
            }
        };

        // act
        var response = _subject.HandleRequest(request);

        // assert
        Assert.Null(response);
    }

}

That’s all the unit test types for now! Anything else that needs testing would be tested outside the Alexa specific logic. But hey! Now we can write some strong and code-covered tested skills which leads to better skills overall

What’s next?

Check out more Alexa Developer Tips here: https://alexdunn.org/tag/alexa/


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile and AI developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.


voicify_logo
I’m the Director and Principal Architect over at Voicify. Learn how you can use the Voice Experience Platform to bring your brand into the world of voice on Alexa, Google Assistant, Cortana, chat bots, and more: https://voicify.com/


Advertisements

Onionizing Xamarin Part 6

For those who just want code: https://github.com/SuavePirate/Xamarin.Onion 

Don’t forget:

  1. Part 1 on the general project structure: Onionizing Xamarin Part 1
  2. Part 2 on our Domain and Application layers: Onionizing Xamarin Part 2
  3. Part 3 on our Infrastructure layer: Onionizing Xamarin Part 3
  4. Part 4 on our Client layer and Xamarin.Forms implementation: Onionizing Xamarin Part 4
  5. Part 5 on creating custom Platform specific logic: Onionizing Xamarin Part 5

A strong and scale-able architecture is important in applications, especially in Mobile Apps. APIs and SDKs are constantly changing, new technology is constantly released, and team sizes are always changing. A solid Onion Architecture can save a development team a lot of time by making it simple to change service implementations, restrict access to certain areas, making logic flow easy to follow, and making testing isolated blocks of code easier.

Some of the important topics this will cover:

  • Separation of Concerns
  • Inversion of Control
  • Dependency Injection
  • Model-View-ViewModel
  • Testability
  • Why all these things are important

Part 6

In this section, we will talk briefly about building useful tests for our solution, and why the Onion pattern makes it easy to break tests out into individual layers.

In this example, we will add a test project whose purpose it to just test the Business layer within our Infrastructure.

Tests.Business

Let’s start with by adding a nUnit project to our solution, or by adding the nuget package to a class library. Xamarin has great documentation on this: https://developer.xamarin.com/guides/cross-platform/application_fundamentals/installing-nunit-using-nuget/

In our project, we also want to install MvvmLight, just like in our Client and Platform layers. We will also need to add references to our Domain.Models, Domain.Interfaces, Application.Models, Application.Interfaces, and Infrastructure.Business projects.

In order to test our Infrastructure.Business project, we will need to create mock versions of our Data project. In our test project, we can create Repository implementations with mock data for each set that we need. For example:

MockGenericRepository.cs

public class MockGenericRepository : IGenericRepository
{
    private List _data;
    public MockGenericRepository()
    {
        _data = new List();
    }

    public void Add(T entity)
    {
        _data.Add(entity);
    }

    public void AddRange(IEnumerable entities)
    {
        _data.AddRange(entities);
    }

    public Task CommitAsync()
    {
        return Task.FromResult(false); // we don't need to explicitly save changes
    }

    public Task FindAsync(Func<T, bool> predicate)
    {
        var entity =_data.Where(predicate).FirstOrDefault();
        return Task.FromResult(entity);
    }

    public Task<IEnumerable> GetAsync(Func<T, bool> predicate)
    {
        var entities =_data?.Where(predicate);
        return Task.FromResult(entities);
    }

    public void Remove(T entity)
    {
        _data.Remove(entity);
    }
}

and MockUserRepository.cs

public class MockUserRepository : MockGenericRepository, IUserRepository
{
    public MockUserRepository()
    : base()
    {
    }
}

Now that we have some mock implementations, we can set up our tests against our Business logic.

UserBusinessTests.cs

public class UserBusinessTest
{
    private IUserService _userService;

    [SetUp]
    public void StartUpIoC ()
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
        SimpleIoC.Default.Register<IUserService, UserService>();
        SimpleIoC.Default.Register<IUserRepository, MockUserRepository>();

        _userService = SimpleIoC.Default.GetInstance();
    }

    [Test ()]
    public async void AddUserTest()
    {
        var result = await _userService.CreateUserAsync(new NewUser
            {
                Email = "test@test.com",
                FullName = "Testy McTest"
            });
        Assert.IsNotNull(result.Data);
    }
}

Now we can test against any of the business logic in our application with a mock layer. The same practice can be applied to test any other layer in the solution as well. The data layer can be tested by mocking the business layer, and so on.

Conclusion

Looking back at all of the components of our Onion Architecture, one might think, “Wow, that’s a lot of code to do a simple task”. It’s important to remember that this architecture is not for every project. It’s focus is on scalability and testability. If your project has the potential to grow into something quite complicated, with many developers involved, this type of solution might work best for you. However, if you’re working on something quick to get out the door, maybe getting right to the point is easier and best for you.

The best parts about the Onion Architecture are its abilities to make drastic changes to tools or services used, without having to rewrite anything but that components implementation as well as making it easy to test individual layers without affecting the others or using real data. It also allows for closer monitoring and management of the codebase; keeping people from making calls directly from one layer to another. The only thing you have to emphasize is, “Are you adding a reference to another project to get something done? If so, you might be doing it wrong”.