Making Nunit tests execute in order

Have you ever wanted to run a given suite of Nunit tests in order? Normally your tests should be created in such a way that execution order is no concern. I stand by that rule, however there is that one edge case were you need to break that rule. The one scenario I came across was with R.NET whereby in a given process you can only initialize one R Engine and once disposed you cannot reinitialize. One of my test happened to deal with disposing the engine and if this test were to run before any other test(s) they would fail.

You will find below a code snippet of how I managed to get this to run in order using Nunit.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using RSamples;

    public class OrderedTestAttribute : Attribute
    {
        public int Order { get; set; }

        public OrderedTestAttribute(int order)
        {
            this.Order = order;
        }
    }

    public class TestStructure
    {
        public Action Test;
    }

    public class SampleTests
    {
        [TearDown]
        public void CleanUpAfterTest()
        {
            REngineExecutionContext.ClearLog();
        }

        [OrderedTest(0)]
        public void Test1(){}

        [OrderedTest(1)]
        public void Test2(){}

        [OrderedTest(2)]
        public void Test3(){}

        [TestCaseSource(sourceName: "TestSource")]
        public void MainTest(TestStructure test)
        {
            test.Test();
        }

        public static IEnumerable<TestCaseData> TestSource
        {
            get
            {
                var assembly = Assembly.GetExecutingAssembly();
                Dictionary<int, List<MethodInfo>> methods = assembly
                    .GetTypes()
                    .SelectMany(x => x.GetMethods())
                    .Where(y => y.GetCustomAttributes().OfType<OrderedTestAttribute>().Any())
                    .GroupBy(z => z.GetCustomAttribute<OrderedTestAttribute>().Order)
                    .ToDictionary(gdc => gdc.Key, gdc => gdc.ToList());

                foreach (var order in methods.Keys.OrderBy(x => x))
                {
                    foreach (var methodInfo in methods[order])
                    {
                        MethodInfo info = methodInfo;
                        yield return new TestCaseData(
                            new TestStructure
                            {
                                Test = () =>
                                {
                                    object classInstance = Activator.CreateInstance(info.DeclaringType, null);
                                    info.Invoke(classInstance, null);
                                }
                            }).SetName(methodInfo.Name);
                    }
                }
            }
        }
    }

DeploymentItemAttribute for NUnit

In a previous post I talked about the proper use of the DeploymentItemAttribute when running MSTests. If you do move to NUnit and have gotten used to depending on this attribute then worry not, I have you covered. Below I have code samples showing the custom attribute I have created to imitate the MSTest behaviour and usage samples. A good scenario of when you might want to use this is when you have a resource used by multiple tests and can be modified by any of the tests, in which case you want each test to have its own fresh copy.


    public class SampleTest : TestBase
    {
        [DeploymentItemAttribute(@"resources\file.txt", "TestName")]
        [Test]
        public void TestName()
        {
        }
    }

    public class TestBase
    {
        [SetUp]
        public void Setup()
        {
            this.DeployItems();
        }

        private void DeployItems()
        {
            var currentType = this.GetType();
            var publicMethodsWithTestAttr = currentType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
                .Where(x => x.CustomAttributes.Any(y=>y.AttributeType == typeof(TestAttribute)));

            //Iterate through each test method and deploy files as necessary
            foreach (MethodInfo testMethodInfo in publicMethodsWithTestAttr)
            {
                var deploymentItemAttrs = testMethodInfo.GetCustomAttributes(typeof(DeploymentItemAttribute));
                foreach (DeploymentItemAttribute deploymentItemAttr in deploymentItemAttrs)
                {
                    this.DeployFile(deploymentItemAttr.Path,deploymentItemAttr.OutputDirectory);
                }
            }
        }

        private void DeployFile(string path, string outputDirectory = null)
        {
            string filePath = path.Replace("/", "\\");

            string originalItemPath = new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), filePath)).LocalPath;
            string originalItemName = Path.GetFileName(originalItemPath);

            string runFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            Debug.WriteLine("DeploymentItem: Copying " + originalItemPath + " to " + runFolderPath);

            string itemPathInBin;
            if (string.IsNullOrEmpty(outputDirectory))
            {
                itemPathInBin = new Uri(Path.Combine(runFolderPath, originalItemName)).LocalPath;
            }
            else if (!string.IsNullOrEmpty(Path.GetPathRoot(outputDirectory)))
            {
                itemPathInBin = new Uri(Path.Combine(outputDirectory, originalItemName)).LocalPath;
            }
            else
            {
                itemPathInBin = new Uri(Path.Combine(runFolderPath, outputDirectory, originalItemName)).LocalPath;
            }

            if (File.Exists(originalItemPath)) // It's a file
            {
                string parentFolderPathInBin = new DirectoryInfo(itemPathInBin).Parent.FullName;

                if (!Directory.Exists(parentFolderPathInBin))
                {
                    Directory.CreateDirectory(parentFolderPathInBin);
                }

                File.Copy(originalItemPath, itemPathInBin, true);

                FileAttributes fileAttributes = File.GetAttributes(itemPathInBin);
                if ((fileAttributes & FileAttributes.ReadOnly) != 0)
                {
                    File.SetAttributes(itemPathInBin, fileAttributes & ~FileAttributes.ReadOnly);
                }
            }
            else if (Directory.Exists(originalItemPath)) // It's a folder
            {
                if (Directory.Exists(itemPathInBin))
                {
                    Directory.Delete(itemPathInBin, true);
                }

                Directory.CreateDirectory(itemPathInBin);

                foreach (string dirPath in Directory.GetDirectories(originalItemPath, "*", SearchOption.AllDirectories))
                {
                    Directory.CreateDirectory(dirPath.Replace(originalItemPath, itemPathInBin));
                }

                foreach (string sourcePath in Directory.GetFiles(originalItemPath, "*.*", SearchOption.AllDirectories))
                {
                    string destinationPath = sourcePath.Replace(originalItemPath, itemPathInBin);
                    File.Copy(sourcePath, destinationPath, true);

                    FileAttributes fileAttributes = File.GetAttributes(destinationPath);
                    if ((fileAttributes & FileAttributes.ReadOnly) != 0)
                    {
                        File.SetAttributes(destinationPath, fileAttributes & ~FileAttributes.ReadOnly);
                    }
                }
            }
            else
            {
                Debug.WriteLine("Warning: Deployment item does not exist - \"" + originalItemPath + "\"");
            }
        }
    }

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = false)]
    public class DeploymentItemAttribute : Attribute
    {
        public DeploymentItemAttribute(string path, string outputDirectory = null)
        {
            this.Path = path;
            this.OutputDirectory = outputDirectory;
        }

        public string Path { get; set; }

        public string OutputDirectory { get; set; }
    }