I first tried starting the nunit-console process using Process.Start and redirecting its output to my web app, however this is not a good solution because the redirected output from the Process class is buffered and you have no control over when it flushes the buffer. I found certain programs like msbuild work great and it flushes constantly, however with nunit-console it holds onto the output until all test cases are complete, which means you can't see the progress of the test cases as they run.
The solution is to use the RemoteTestRunner nunit class and create an event listener class that implements the NUnit.Core.EventListener interface:
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() + "] ";
}
}
}
After that, create a TestRunner class that calls RemoteTestRunner and uses your sexy new event listener class:
public static class TestRunner
{
public static bool InProgress = false;
public static StringBuilder Output = new StringBuilder();
private static RemoteTestRunner Runner;
public static void Start(string fileName)
{
InProgress = true;
Output = new StringBuilder();
StartTests(fileName);
}
private static void StartTests(string fileName)
{
//start nunit.
var testPackage = new TestPackage(fileName);
Runner = new RemoteTestRunner();
Runner.Load(testPackage);
var nunitEventListener = new NUnitEventListener();
nunitEventListener.CompletedRun += new EventHandler(nunitEventListener_CompletedRun);
nunitEventListener.Output = Output;
Runner.BeginRun(nunitEventListener);
}
static void nunitEventListener_CompletedRun(object sender, EventArgs e)
{
if (Runner != null)
{
Runner.CancelRun();
Runner = null;
}
InProgress = false;
}
}
Now call the TestRunner class in your ASP.NET MVC Controller:
public class TestController : ApplicationController
{
//GET: /Test/Index/
public ActionResult Index()
{
TestRunner.Start(@"C:\PathToTestProject\bin\Release\SystemTest.dll");
return View();
}
//POST: /Test/GetOutput/
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetOutput()
{
var result = new
{
InProgress = TestRunner.InProgress,
Output = TestRunner.Output.ToString()
};
return Json(result);
}
}
Lastly, create a simple view to show the output as the test cases run. Mine uses dojo but it can easily be modified to work with jquery or vanilla javascript:
<script type="test/javascript">
var nunit = {
init: function () {
nunit.get();
},
get: function () {
//ajax call.
ajax.post("test/getoutput/", {}, nunit.display);
},
display: function (result) {
console.debug(result);
dojo.byId("output").innerHTML = result.Output.length > 0 ? result.Output : dojo.byId("output").innerHTML;
if (result.InProgress)
window.setTimeout(nunit.get, 10000);
}
};
dojo.addOnLoad(nunit.init);
</script>
<div id="output">
The tests are running, please wait....
</div>
That's it... Hope this helps some others, as all of the examples online of RemoteTestRunner (including on stackoverflow) pass in a NullListener, which means you can't capture the output of the test run.