Integration Tests in Service Fabric – NUnitTestAssemblyRunner

In the previous post, I explained the inner workings of the communication listener and how it uses NUnitLite and a custom code to cache test names.

In this post I’ll show how the NUnitTestAssemblyRunner is used to run individual tests when the client calls the Run method with individual test names. Without further ado

public Task<Result> Run(string testName) {
    return Task.Run(() => {
        var resultListener = new ResultListener();
        var provider = new StatefulServiceProviderListener<TService>(statefulService);
        var eventSourceTestListener = new EventSourceTestListener();
        var compositeListener = new CompositeListener(provider, resultListener, eventSourceTestListener);

        var fullNameFilter = new FullNameFilter(testName);
        runner.Run(compositeListener, fullNameFilter);

        return resultListener.Result;
    });
}

The Run method executes the actual test run on a dedicated worker thread. With the setting of SynchronousEvents set to true introduced in the previous blog post, all event transitions would happen synchronously on the calling thread. Due to the asynchronous nature of Service Fabric APIs it is required to adhere to this definition and not do any heavy-lifting on the calling thread directly. The FullNameFilter provided by NUnit is used to determine the exact test name that needs to be executed. With a custom written ResultListener, it is possible to determine the outcome of the test execution and report the result back to the client that is calling the run method.

class ResultListener : ITestListener {
    public Result Result { get; private set; }

    public void TestFinished(ITestResult result) {
        if (!result.Test.IsSuite) {
            Exception exception = null;
            switch (result.ResultState.Status) {
                case TestStatus.Passed:
                    break;
                case TestStatus.Inconclusive:
                    exception = new InconclusiveException(result.Message);
                    break;
                case TestStatus.Skipped:
                    exception = new IgnoreException(result.Message);
                    break;
                case TestStatus.Warning:
                    break;
                case TestStatus.Failed:
                    exception = new AssertionException($"{result.Message}{Environment.NewLine}{result.StackTrace}");
                    break;
            }

            Result = new Result(result.Output, exception) {
                Duration = result.Duration,
                StartTime = result.StartTime,
                EndTime = result.EndTime
            };
        }
    }
    
    // rest omitted
}

The result listener determines the state of the test by switching over the TestStatus enumeration and setting the Result property accordingly. Β The Result property is returned back to the caller when the run method of the runner completed synchronously. Last but not least another test listener called StatefulServiceProviderListener is used to add the stateful service instance to the test properties. With the test properties bag, it is possible to build an interception like mechanism that can be leverage to automagically set the dependencies on the tests to the Service Fabric SDK infrastructure if required. More on that in the next post.

class StatefulServiceProviderListener<TService> : ITestListener 
    where TService : StatefulService {

    public StatefulServiceProviderListener(TService service) {
        this.service = service;
    }

    public void TestStarted(ITest test) {
        test.Properties.Set("StatefulService", service);
    }
    
    // rest omitted

    TService service;
}

In the next and last post in this series, I will walk you through the interception mechanism that allows to inject dependencies into the tests that leverage the properties bag of NUnit.

About the author

Daniel Marbach

3 comments

Recent Posts