Unit testing in LightSwitch (part 1)

Introduction

Unit testing a very common practice in software development. In LightSwitch there is no out-of-the-box support for unit testing. This is probably an inheritance of the fact that in the early LightSwitch days, non-professional developers where seen a core public.

Why is unit testing important?

I will not give an in extenso tutorial on unit testing here (just Google it, if you are completely unfamiliar with unit testing) but I just want to share my view on unit testing and why it’s so important when you are serious about building and maintaining reliable software.

In my view, unit testing has a double intention and moreover both “intentions” enforce each other.

1. Unit testing is a way to improve your code design

Writing unit tests will force you to develop code in a very systematic way, at least under the assumption that you work in a way where you develop first your test and progressively add functionality to the code base to get eventually the test running correctly. There is a lot of literature about this subject. Google on “Red Green Refactor”.

 

2. Unit tests are a safety net for future changes to your code base.

If every business requirement is nicely covered by unit tests, you will feel very comfortable to add new functionality to the code base, even when you didn’t write the initial software. The reason is because when you break existing functionality the unit tests will warn you. A good practice is to run unit tests in “continuous integration mode”, meaning that when a developer checks in code in the source control system, automatically all unit tests are run and potential breaking changes are immediately detected.

 Are unit tests important in a LightSwitch project?

The answer is absolutely: yes.

Obviously, the added-value of unit tests grows exponentially with the complexity of your business logic. Nonetheless, there is a substantial difference with regular .net line of business application projects developed with a custom (or in house) framework. LightSwitch is as a framework very much self-containing and covers in an end-to-end way all aspects of a line of business application. There is no need to the the LightSwitch framework itself. That might be different in a custom framework, where at least the integration parts of the application business logic and the underlying framework code needs to be tested.  In short, this makes that probably you need to write less tests for a line of business app written in LightSwitch. Indeed… again another advantage !

What type of unit tests do we have in mind?

It is clear we are talking here about business logic unit tests. So, tests covering the logic present in the domain model of the application. This is not necessarily “server-only” material. A ‘create new customer’ method can run on the client as well, and is also in scope of a unit test.

Strictly speaking a business logic unit test, tests the business logic layer in complete isolation (that why it’s called “unit”). In a custom line of business framework, this is achieved by using “mocking” functionality, in such a way the persistence layer is not directly involved in the unit test. That would be very difficult in LightSwitch to achieve this. Therefor, the type of tests that I have in mind are better called business logic “integration tests”.  (nonetheless, I prefer to simply call them unit tests)

Basically, there is nothing wrong with taking the “integration” test direction, but there is one drawback: every test that is run, will “pollute” the database with new material that is created during the test execution. Luckily, we can cope with this, by making use of a transaction scope which will rollback everything at the end of every test.

Give me some examples.

You will see in a further post, that incorporating unit tests in LightSwitch is not so easy. I’ll keep the details for the next post.

I’ll provide here already a very small sample of what we want to do.

Imagine I have a very simple application which manages Customers and Orders and we simply focus on the following business logic which we would want to be covered by unit tests:

  • Business Logic Rule 1: A customer has 4 properties: LastName, FirstName, DateOfBirth and City. We’ll introduce the rather silly rule on city: “when a city contains an ‘x’ character, a validation message should be shown that an ‘x’ is not allowed.
  • Business Logic Rule 2: When a new customer is inserted into the system and this customer has her birthday in the current month, a free order is placed for this customer. Obviously, when the customer’s birthday is not in the current month, no free order is placed.

So, this is some very simple business logic that we want to submit to testing.

The type of logic that is tested here is

  1. validation logic
  2. side-effect processing

This is not yet covering the whole spectrum of tests that I want to cover in LightSwitch, but it’s meant as a first indication of what should be possible. I’ll cover in later posts:

  • testing dedicated queries. E.g. get all customers in France, having more than 5 orders.
  • testing permissions. E.g. prove that the current user can only see customers to which she is related as account manager (obviously, this would require an account manager field)

customers Orders

For completeness, I provide first the implementation of the above logic in our LightSwitch domain model:

 public partial class Customer
    {
        partial void LastName_Validate(EntityValidationResultsBuilder results)
        {
            if (this != null && !string.IsNullOrEmpty(this.City) && this.City.Contains('x'))
            {
                results.AddEntityError("no x please");
            }
        }
    }
    public partial class ApplicationDataService
    {
        partial void Customers_Inserting(Customer entity)
        {
            if (entity.DateOfBirth.HasValue && entity.DateOfBirth.Value.Month == (DateTime.Now.Month))
            {
                Order freeOrder = entity.Orders.AddNew();
                freeOrder.OrderDate = DateTime.Now;
                freeOrder.OrderSummary = "oh yes, there is something as a free lunch";
            }
        }
    }

Simple Indeed.

Show me the tests

I’m using the NUnit test framework (http://www.nunit.org), which can be injected via Nuget. Unfortunately, you will not be able to use  the classic approach of

  1. adding a dedicated test assembly and
  2. using a test runner from visual studio and run your tests either individual or in bulk.

I’ll elaborate further in a later post, why this is not possible and I’ll show you what is the workaround that I’m using. So, just until now, be confident that you will be able to run following tests in LightSwitch. Maybe not in the most optimal way, but it’s possible.

 public class CustomerTests : LightSwitchTransactionalTestBase
    {
        [Test]
        [Description("When a customer city contains an x character, a validation error is shown")]
        public void WhenCustomerCityContainsValueXValidationErrorShouldBeShown()
        {
            Customer customer = CurrentDataWorkspace.CreateDefaultTestCustomer();
            customer.City = "xtest city";

            Assert.That(() => CurrentDataWorkspace.ApplicationData.SaveChanges(),
                Throws.Exception.TypeOf<Microsoft.LightSwitch.ValidationException>());

            string expectedValidationMessage = "no x please";
            var actualValidationresults = customer.Details.ValidationResults;
            Assert.That(actualValidationresults.Count(), Is.EqualTo(1), "There should be exactly one validation result");
            var actualValidationresult = actualValidationresults.First();
            Assert.That(actualValidationresult, Is.Not.Null, "The validation result should not be null");
            Assert.That(actualValidationresult.Message, Is.EqualTo(expectedValidationMessage));
        }

        [Test]
        [Description("When a customer city does not contain an x character, the validation result should be ok")]
        public void WhenCustomerCityDoesNotContainsValueXNoValidationErrorShouldBeShown()
        {
            Customer customer = CurrentDataWorkspace.CreateDefaultTestCustomer();

            CurrentDataWorkspace.ApplicationData.SaveChanges();

            var actualValidationresults = customer.Details.ValidationResults;
            Assert.That(actualValidationresults.Count(), Is.EqualTo(0), "There should be no validation result");
            Assert.That(actualValidationresults.HasErrors, Is.EqualTo(false), "The HasError property of the validation results should be false");
        }

        [Test]
        [Description("When a customer is inserted with a birthday in the current month, a free order should is placed automatically")]
        public void WhenInsertingACustomerWithBirthdayInCurrentMonthAFreeOrderIsPlaced()
        {
            Customer customer = CurrentDataWorkspace.CreateDefaultTestCustomer();

            customer.DateOfBirth = new DateTime(1969, DateTime.Now.Month, 28);
            CurrentDataWorkspace.ApplicationData.SaveChanges();
            var freeOrder = CurrentDataWorkspace.ApplicationData.Orders.Where(o => o.Customer.Id == customer.Id).Execute().FirstOrDefault();

            Assert.That(freeOrder, Is.Not.Null, "there should be an order record");
            string expectedResult = "oh yes, there is something as a free lunch";

            Assert.That(freeOrder.OrderSummary, Is.EqualTo(expectedResult), "the free order text is incorrect");
            Assert.AreEqual(expectedResult, freeOrder.OrderSummary);
        }

        [Test]
        [Description("When a customer is inserted with a birthday not in the current month, no automatic free order should is placed")]
        public void WhenInsertingACustomerWithBirthdayNotInCurrentMonthNoFreeOrderIsPlaced()
        {
            Customer customer = CurrentDataWorkspace.CreateDefaultTestCustomer();
            customer.DateOfBirth = new DateTime(1969, DateTime.Now.Month, 28).AddMonths(-1);
            CurrentDataWorkspace.ApplicationData.SaveChanges();

            var freeOrder = CurrentDataWorkspace.ApplicationData.Orders.Where(o => o.Customer.Id == customer.Id).Execute();
            Assert.That(freeOrder.Count(), Is.EqualTo(0), "There should be no order placed");
        }
    }

On which infrastructure are these tests depending?

Well, if you are familiar with unit testing in .Net, you will immediately recognize that the above tests are quite “orthodox” and that’s a very good starting point. I think that my test approach can be fine-tuned on the level of how the tests are executed and I hope that this article will invite other community members to look also into this matter, but what is definitely ok, is the way how the tests are written. What I mean is that changes the way how tests are trigger will not influence the way how we write tests.

You’ll first notice that my test class derives from a base class called LightSwitchTransactionalTestBase. The name of the base class reveals immediately that

  • it will make the tests transactional. That’s great because we don’t want that our database is polluted with test data;
  • it will also open up access to the LightSwitch domain model. Normally, you can only access the LightSwitch back-end via the odata service end points. Luckily, since preview 2, we have the famous ServerApplicationContext at our disposal ! Without this ServerApplicationContext it would be simply  impossible to write tests.

Here is the complete implementation of the base class:

using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using System.Web;

namespace LightSwitchApplication.TestInfrastructure
{
    [TestFixture]
    public class LightSwitchTransactionalTestBase : IDisposable
    {
        public static DataWorkspace CurrentDataWorkspace;

        public LightSwitchTransactionalTestBase()
        {
            if (ServerApplicationContext.Current == null)
            {
                this.ownServerContext = true;
                ServerApplicationContext.CreateContext();
            }
            else
            {
                this.ownServerContext = false;
            }
            CurrentDataWorkspace = ServerApplicationContext.Current.DataWorkspace;
        }
        private TransactionScope _scope;

        [SetUp]
        public void SetUp()
        {
            _scope = new TransactionScope();
        }

        [TearDown]
        public void TearDown()
        {
            CurrentDataWorkspace.ApplicationData.Details.DiscardChanges();
            _scope.Dispose();
        }

        public void Dispose()
        {

            if (this.ownServerContext &&
                ServerApplicationContext.Current != null &&
               !ServerApplicationContext.Current.IsDisposed)
            {
                ServerApplicationContext.Current.Dispose();
            }

        }

        public bool ownServerContext { get; set; }
    }
}

So, even if your tests is doing a SaveChanges on the dataworkspace, things will be rolled back !

I’m using also another helper class, but it’s purpose is more of a cosmetic/organizational nature.

In mosts tests, related to our customer entity, we will need to create an in memory customer. We’ll the CustomerTestHelper class (which is an extension method class)  is providing some comfort here:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LightSwitchApplication.UnitTests
{
    internal static class CustomerTestHelper
    {
        internal static Customer CreateDefaultTestCustomer(this DataWorkspace CurrentDataWorkspace)
        {
            Customer customer = CurrentDataWorkspace.ApplicationData.Customers.AddNew();
            customer.FirstName = "test first name";
            customer.LastName = "test last name";
            customer.City = "test city";
            customer.DateOfBirth = DateTime.Now;
            return customer;
        }
    }
}

How can we run this tests?

You can’t, at least not with the material I presented so far :)

Be patient and confident. I’ll explain in a next post.

Until now, I’ll run them for you. Here are the results:

results

Conclusion

LightSwitch goes unit testing !

For me this a very serious matter and I really hope I will get some support by some Pro’s in the LightSwitch community to improve my approach, which is still, in a way, sub-optimal.

I’m convinced that having the possibility of unit testing in LightSwitch is the most important trigger to get the real professional .net developers on board and this will create a point of no return for LightSwitch.