Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Why NUnit Test Context is Our Best Friend?

0.00/5 (No votes)
22 Jun 2017 1  
In this post, I'd like to talk a little bit more about the TestContext class and the opportunities that it brings us.

In the Creating custom attributes with internal NUnit features article, I mentioned the ITest interface that gives us access to test properties. In this post, I'd like to talk a little bit more about the TestContext class and the opportunities that it brings us. First of all, let's define some kind of challenge that we want to solve at the end using the above-mentioned TestContext class and especially its static CurrentContext property.

Challenge: To provide infrastructure for saving logs for each failed NUnit test with splitting by sub-folders.

First of all, we need to understand where we'd like to save our logs. Working with the file system is painful sometimes because we need to be careful with absolute paths. In order to solve this problem, we need to recall that NUnit tests are always run from a DLL file, doesn't matter what way of running tests we use (Visual Studio, NUnit GUI, command line or continuous integration system). This similarity allows us to utilize the first property of the CurrentContextTestDirectory. Let's see an example:

using NUnit.Framework;
using NUnit.Framework.Interfaces;
using System.IO;

namespace TestContextExamples
{
    [TestFixture]
    public class FoldersAndNamesExamples
    {
        private string CurrentTestFolder = TestContext.CurrentContext.TestDirectory;
        private DirectoryInfo LogsFolder;
        private DirectoryInfo SubTestFolder;

        [OneTimeSetUp]
        public void OneTimeSetUp()
        {
            LogsFolder = Directory.CreateDirectory(Path.Combine(CurrentTestFolder, "Logs"));   
        }

        [SetUp]
        public void SetUp(){ }

        [Test]
        public void MyTest()
        {
            // test logic here
        }

        [TearDown]
        public void TearDown(){ }
    }
}

The private CurrentTestFolder string points us to the directory where the DLL file with NUnit tests is located. For example, if we run tests from Visual studio, it's usually the [solution]\[project]\bin\Debug\ or [solution]\[project]\bin\Release\ folders. According to our initial goal, I added a structure of NUnit methods and a couple of private members of the class which corresponds to the common logs folder and sub-folders for each test: LogsFolder and SubTestFolder.

In the OneTimeSetUp() method (which is executed only once), we create the “Logs” folder inside the CurrentTestFolder. So, before any test is started, we have the place where we can save our logs. But what about splitting logs by sub-folders corresponding to the test names? It's time to appeal to the CurrentContext again! Let's see how we can modify the SetUp() method:

[SetUp]
public void SetUp()
{
    var currentTestName = TestContext.CurrentContext.Test.Name;
    SubTestFolder = Directory.CreateDirectory(Path.Combine(LogsFolder.FullName, currentTestName));     
}

As we see from the method's body above, CurrentContext allows us to get a current test name by accessing its Test.Name property. Then, we just create an appropriate folder inside the “Logs” folder. Due to the purpose of the method followed after the [SetUp] attribute, it's executed before each test, i.e., we create a new folder for each test according to its name.

Except for a test name, TestContext.CurrentContext.Test property gives us access to the following data:

  • ClassName
  • FullName
  • test ID
  • MethodName
  • Properties bag (see later)

Only one task remains – to provide saving logs for failed tests only. First of all, we need to understand what we mean under the “failed tests” because a test in the NUnit world can have different states: passed, failed, failed with an error, skipped, inconclusive, etc. Let's decide that we'd like to save our logs for those tests which are failed because of assertions and unexpected exceptions. All the rest we don't care. Another question is where we should perform this check (if a test is failed or not)? The answer is in the method marked with the [TearDown] attribute because it's executed after each test and this is the correct place where we know the test result already. Here is the final code of our infrastructure:

using NUnit.Framework;
using NUnit.Framework.Interfaces;
using System.IO;

namespace TestContextExamples
{
    [TestFixture]
    public class FoldersAndNamesExamples
    {
        private string CurrentTestFolder = TestContext.CurrentContext.TestDirectory;
        private DirectoryInfo LogsFolder;
        private DirectoryInfo SubTestFolder;

        [OneTimeSetUp]
        public void OneTimeSetUp()
        {
            LogsFolder = Directory.CreateDirectory(Path.Combine(CurrentTestFolder, "Logs"));      
        }

        [SetUp]
        public void SetUp()
        {
            var currentTestName = TestContext.CurrentContext.Test.Name;
            SubTestFolder = Directory.CreateDirectory
                            (Path.Combine(LogsFolder.FullName, currentTestName));
        }

        [Test]
        public void MyTest()
        {
            // test logic here
        }

        [TearDown]
        public void TearDown()
        {
            var testResult = TestContext.CurrentContext.Result.Outcome;

            if (Equals(testResult, ResultState.Failure) ||
                Equals(testResult == ResultState.Error))
            {
                // save your logs here
            }
        }
    }
}

As you can see from the TearDown() method, we get the TestContext.CurrentContext.Result.Outcome property of the current test. Then, we compare it with known values of the ResultState enum and make a decision: to save or not to save our logs for this specific test.

What else can the CurrentContext give us? For example, Random numbers generator:

[Test]
public void RandomGeneratorTest()
{
    Console.WriteLine(TestContext.CurrentContext.Random.Next());
    Console.WriteLine(TestContext.CurrentContext.Random.NextDecimal());
    Console.WriteLine(TestContext.CurrentContext.Random.NextBool());
}

The Random property of the Randomizer class provides lots of methods for obtaining random numbers of different types (bool, integer, float, long, decimal, etc.). This is very useful in some cases if we want to make our unit tests more robust and test more different combinations. Another interesting example of the CurrentContext usage is shown below:

[Test]
[Category("Functional"), Category("Release")]
public void CategoriesPropertiesTest()
{
    foreach (var category in TestContext.CurrentContext.Test.Properties["Category"])
    {
        Console.WriteLine(category);
    }
}

I've mentioned that CurrentContext gives us a way to get the Property bag of a test. The example above shows exactly that case when we can get access to different NUnit test properties. As you might know, an NUnit test can have a lot of different attributes: Description, Order, Repeat, Category, Bug, etc. If we know what we're looking for (the name of the property), we're able to get it, check its value and adjust some logic inside the test or SetUp/TearDown methods.

Summary

Internal NUnit features have a lot of interesting methods and properties inside. As we've just seen, the TestContext contains many useful properties which can be and should be used by QA Automation engineers in order to make our tests more readable, flexible and maintainable. Explore it, try it and have fun!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here