Unit Testing in LightSwitch (part 2)

Introduction

Download the sample: NUnitTesting . It will be easier to follow. Make sure you have preview 2 of LightSwitch installed !

Why can’t we use the traditional test runner approach with LightSwitch

Honestly, if I could give over here a very precise and complete answer, I could probably solve it also.

In a traditional approach unit tests are stored in a dedicated .net assembly and are triggered from a kind of test runner which is running in a separate process (and often, depending on which unit test framework you use, integrated in visual studio).

Obviously, inside a test you will need to have access to the material that you want to test. In a LightSwitch app, this means: the domain logic. The only way to access the domain logic of the app is via:

  1. the odata endpoint
  2. the ServerApplicationContext (new in preview 2).

Option one will work perfectly in the visual studio way, but it’s in my view sub-optimal because ODATA (and REST in general) and a distributed transaction context does not go together.

Option two will work but there is the problem that setting up the ServerApplicationContext requires that the thread which is doing this is the owner of the HTTP context. Finding a solution for this, is clearly beyond the scope of my technical expertise, so I had to find a workaround.

I invite you all, to check if my findings are correct. Add a test project to your LightSwitch solution and try if you get things up and running.

 

What’s my approach?

We’ll have to work in 2 steps.

  • Step 1:  we need to instantiate a “test runner” inside the server project. The tests itself are located also inside the server project. The test runner is doing nothing more than it says: starting the tests and giving back the results.
  • Step 2: we need a way to trigger the test runner from outside lightswitch even if the project is NOT running. That’s the same as in the traditional approach. The only thing for which i have currently no solution, is to start one test individually, but debugging a test is working perfectly. My prefered approach is that we use a simple html page on which we can start the test execution and get back the test results, formatted in a way that looks quite close (although currently a bit “spartan”) to what we have with traditional test runners.

 

Step 1: implement the test runner inside the server project.

Here we go:

using NUnit.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;

namespace LightSwitchApplication.TestInfrastructure
{
    public static class LightSwitchTestRunner
    {
        public static bool InProgress = false;
        public static StringBuilder Output = new StringBuilder();
        private static SimpleTestRunner Runner;

        public static string Start()
        {
            InProgress = true;
            Output = new StringBuilder();
            StartTests();
            return Output.ToString();
        }

        private static void StartTests()
        {
            CoreExtensions.Host.InitializeService();
            string fileName = Assembly.GetExecutingAssembly().Location;
            var testPackage = new TestPackage(fileName);
            Runner = new SimpleTestRunner();
            Runner.Load(testPackage);
            var nunitEventListener = new NUnitEventListener();
            nunitEventListener.Output = Output;

            var result = Runner.Run(nunitEventListener, TestFilter.Empty, true, LoggingThreshold.All);
        }
    }
}

Thanks to NUnit, we can do this.

We need also an event listener:

using NUnit.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LightSwitchApplication.TestInfrastructure
{
    public class NUnitEventListener : NUnit.Core.EventListener
    {
        public event EventHandler CompletedRun;
        public StringBuilder Output;
        private int TotalTestsPassed = 0;
        private int TotalTestsErrored = 0;

        public void RunStarted(string name, int testCount)
        {
            Output.AppendLine(TimeStamp + "Running " + testCount + " tests in " + name + "<br/><br/>");
            TotalTestsPassed = 0;
            TotalTestsErrored = 0;
        }

        public void RunFinished(System.Exception exception)
        {
            Output.AppendLine(TimeStamp + "Run errored: " + exception.ToString() + "<br/>");
            //notify event consumers.
            if (CompletedRun != null)
                CompletedRun(exception, new EventArgs());
        }

        public void RunFinished(TestResult result)
        {
            Output.AppendLine(TimeStamp + "<label class='normal " + (TotalTestsErrored == 0 ? "green" : "red")
                + "'>" + TotalTestsPassed + " tests passed, " + TotalTestsErrored + " tests failed</label><br/>");
            Output.AppendLine(TimeStamp + "Run completed in " + result.Time + " seconds<br/>");
            //notify event consumers.
            if (CompletedRun != null)
                CompletedRun(result, new EventArgs());
        }

        public void TestStarted(TestName testName)
        {
            Output.AppendLine(TimeStamp + testName.FullName + "<br/>");
        }

        public void TestOutput(TestOutput testOutput)
        {
            if (testOutput.Text.IndexOf("NHibernate:") == -1)
                Output.AppendLine(TimeStamp + testOutput.Text + "<br/>");
        }

        public void TestFinished(TestResult result)
        {
            if (result.IsSuccess)
            {
                Output.AppendLine(TimeStamp + "<label class='green normal'>Test Passed!</label><br/><br/>");
                TotalTestsPassed++;
            }
            else
            {
                Output.AppendLine(TimeStamp + "<label class='red normal'>Test Failed!<br/>" + result.Message.Replace(Environment.NewLine, "<br/>") + "</label><br/>");
                TotalTestsErrored++;
            }
        }

        public void UnhandledException(System.Exception exception)
        {
            Output.AppendLine(TimeStamp + "Unhandled Exception: " + exception.ToString() + "<br/>");
        }

        public void SuiteStarted(TestName testName)
        {
        }

        public void SuiteFinished(TestResult result)
        {
        }

        private string TimeStamp
        {
            get
            {
                return "[" + DateTime.Now.ToString() + "] ";
            }
        }
    }
}

 

Voilà. Problem one is solved. Now we need a way to trigger the test runner which is nothing more than  LightSwitchTestRunner.Start().

Starting the tests from a simple html page.

We’ll use a simple html page which connects via a Json Jquery call to a Web Api Controller.

This is the html page:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Overview tests</title>
    <script src="Scripts/jquery-1.9.0.js" type="text/javascript"> </script>
    <script src="Scripts/jquery-1.9.0-vsdoc.js" type="text/javascript"> </script>
    <script type="text/javascript">
        function showAll() {
            $.getJSON("api/unittest/",
               function (data) {
                   $.each(data, function (key, val) {
                       var str = val + '<br/>'
                       $('<li/>', { html: str })
                       .appendTo($('#TestResults'));
                   });
               });
        }
        function CleanAll() {
            $("#TestResults").text("");
        }
 </script> 

</head>
<body>
    <div>
        <h1>Test Results</h1>
         <input type="button" value="Run Tests..." onclick="showAll();" />
        <input type="button" value="Clean..." onclick="CleanAll();" />
    </div>

    <div>
        <p id="TestResults" />
    </div>
</body>
</html>

And this is the api web controller:

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

namespace LightSwitchApplication.TestInfrastructure
{
    public class UnitTestController : ApiController
    {
        public IEnumerable<string> Get()
        {
            return new string[]
            { LightSwitchTestRunner.Start()
        };
        }
    }
}

 

As you can see, the UnitTestController is from inside the server dll triggering the whole unit test machinery !

Don’t forget to update the global.asax for the correct routing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Http;
namespace LightSwitchApplication
{
    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            // for Unit Test Controller
            RouteTable.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = System.Web.Http.RouteParameter.Optional }
                 );
        }
    }
}

There are some Nuget packages involved for the Web Api, Jquery and NUnit in the server project. But the easiest way is simply to download my sample.

 

Other options: run the unit tests from the silverlight client.

Feels like long time ago I spoke about SignalR. Well, with Signal R, you could start the TestRunner also from the silverlight client. SignalR acts then as an implementation of the command table pattern.

It’s a cool approach, but there is the disadvantage that your complete LightSwitch project must be running. Indeed, you need to see the screen, don’t you? So, I prefer the html approach. Simply, write a test, compile and open:

http://localhost:55147/TestRunner.html  (change to your tcp port) and you’ll see:

results

 

 Conclusion

Enjoy and don’t hesitate to provide feedback for potential improvement !