In my previous post, Overview of the TestContext in NUnit. Part 1: Static properties and methods, I went over the static properties and methods that are available on the TestContext. I had briefly mentioned the CurrentContext and in this post we are going to dive deeper into this property.

The CurrentContext gets created separately for each test but is still accessible anywhere within the TestFixture. It holds information about the current test, fixture execution, and even provides you with a Randomizer class that you can use inside your test to build up random data.

Let's dig into the CurrentContext!


Current Context - Properties

  • Test - This gets you a representation of the current test. It's properties are:

    • ID - The unique Id of the test
    • Name - The name of the test, whether set by the user or generated automatically
    • FullName - The fully qualified name of the test
    • ClassName - The fully qualified name of the class
    • MethodName - The name of the method representing the test, if any
    • Properties - An IPropertyBag of the test properties

If you recall the example we were using in our previous posts, we had this HelloWorld test.

using NUnit.Framework;

namespace BlogSamples.NUnit  
{
    [TestFixture]
    public class HelloWorld
    {
        [Test]
        public void Hello()
        {
            string greeting = BlogSamples.HelloWorld.Hello("get-testy");
            Assert.That(greeting.Equals("Hello, get-testy.", StringComparison.Ordinal));
        }
    }
}

If we were to access TestContext.CurrentContext.Test from inside the method Hello, we would see these values:

  • ID = "0-1001"
  • Name = "Hello"
  • FullName = "BlogSamples.NUnit.HelloWorld.Hello"
  • ClassName = "BlogSamples.NUnit.HelloWorld"
  • MethodName = "Hello"
  • Properties = { Keys: { } }

A few things to note about the values.

  • The IPropertyBag for Properties is empty. Since there are no extra attributes putting properties onto this test case, it is empty.
  • The ID is not guaranteed to be the same each test run. It is just to uniquely identify that execution of the test.

Now let's take this same method, but change the Name of it and also add a property.

[TestCase(
 TestName = "Hello, get-testy.",
 Description = "Verify we can get a properly formed message back from HelloWorld.",
 Author = "James Penning")]
public void Hello()  
{
    string greeting = BlogSamples.HelloWorld.Hello("get-testy");
    Assert.That(greeting.Equals("Hello, get-testy.", StringComparison.Ordinal));
}

Now when we access TestContext.CurrentContext.Test, we would see these values:

  • ID = "0-1001"
  • Name = "Hello, get-testy."
  • FullName = "BlogSamples.NUnit.HelloWorld.Hello, get-testy"
  • ClassName = "BlogSamples.NUnit.HelloWorld"
  • MethodName = "Hello"
  • Properties = { Keys: { "Description": "Verify we can get a properly formed message back from HelloWorld.", "Author":"James Penning" } }

There are a lot of different attributes that you can add in order to fill in the properties. Description, Author, Category, etc.

You also may have noticed that I changed the attribute that declares Hello as a test case. Test and TestCase are subtly different. The basic difference is that you can have multiple TestCase attributes on a method, which will create multiple tests based off the values set in each attribute. Whereas with the Test attribute, you can only have one. Be on the look out to a post I will be making about all the different nunit attributes you can use!



  • Result - Gets a representation of the test result.
    • Outcome - A ResultState representing the outcome of the test.
    • PassCount - The number of tests that passed.
    • InconclusiveCount - The number of tests that were Inconclusive.
    • SkipCount - The number of tests that were skipped.
    • WarningCount - The number of tests that had warnings.
    • FailCount - The number of tests that failed.
    • Message - Any message that nunit defines based off test results. IE: "Message: One or more child tests were ignored" would be the message if there were tests that were ignored.
    • StackTrace - Any exception was thrown inside of a test.

The ResultState is accessible during the execution of a test, however, it is best to only use it in the teardown stage. It will have all the values populated at that time.

  • ResultState
    • Status - A TestStatus with four possible values.
    • Label - An optional string value, which can provide sub-categories for each Status.
    • Site - A FailureSite value.

These are the four possible values for Status. If you are familiar with the TestResult.xml output from a test execution, these are what get associated with the test-case nodes. I gave an example in my Part 1 post.

  • TestStatus
    • Inconclusive
    • Skipped
    • Passed
    • Warning
    • Failed

These are the possible values for the FailureSite. This will indicate where the failure occurred in the execution of the test execution.

  • FailureSite
    • Test
    • SetUp
    • TearDown
    • Parent
    • Child

One practical way you might use the ResultState would be to check if a test failed. If you have tests that you are running in serial that require the previous to pass, you would want to stop further execution to prevent cascading failures. Here's how you can easily do this:

private bool _stopFurtherExecution = false;

[SetUp]
public void SetUp()  
{
    if (_stopFurtherExecution)
    {
        Assert.Inconclusive("Previous test failed");
    }
}

[TearDown]
public void TearDown()  
{
    bool status = TestContext.CurrentContext.Result.Outcome.Status;
    if(status.Equals(NUnit.Framework.Interfaces.TestStatus.Failed))
    {
        _stopFurtherExecution = true;
    }
}

You could put this in a base class and then have any TestFixture that you want to follow this behavior and inherit from it.


TestDirectory

This will return the full file path of the directory in which the current executing assembly is located.


WorkDirectory

This will return the full file path of where the test is being executed.

We can access the TestDirectory and WorkDirectory like this.

TestContext.WriteLine($"Current TestDirectory: {TestContext.CurrentContext.TestDirectory}");  
TestContext.WriteLine($"Current WorkDirectory: {TestContext.CurrentContext.WorkDirectory}");  

Which, when we execute the tests, would produce a result that looks like this.

<!-- Command to execute tests -->  
D:\SourceCode\BlogSamples>  D:\SourceCode\nunit-console\bin\Release\nunit3-console.exe .\BlogSamples.NUnit\bin\Debug\BlogSamples.NUnit.dll

NUnit Console Runner 3.7.0  
Copyright (C) 2017 Charlie Poole

Runtime Environment  
   OS Version: Microsoft Windows NT 10.0.14393.0
  CLR Version: 4.0.30319.42000

Test Files  
    .\BlogSamples.NUnit\bin\Debug\BlogSamples.NUnit.dll

<!-- Output from our tests -->  
=> BlogSamples.NUnit.HelloWorld.Hello, get-testy.
Current TestDirectory: D:\SourceCode\BlogSamples\BlogSamples.NUnit\bin\Debug  
Current WorkDirectory: D:\SourceCode\BlogSamples  

You can see that the TestDirectory is where the actual assembly that we are loading to execute tests and the WorkDirectory is where we are currently at in the powershell window.


Random

This one is probably my favorite property to use. The Random property returns a Randomizer object which can aid you in generating random values to use in your tests. In using Random, if you don't change the assembly or the seed used, you can repeat values on reruns. Which is really cool when you want to send in random data to methods but also be able to rerun tests with the same data if they fail.

You might be thinking, "Why would I want to use this instead of using System.Random directly?" Well, there are a couple reasons why, but the main two include:

  1. Uniform methods. These methods extend the behavior on a wide range of types which System.Random does not include.
    • IE. NextLong, NextLong(long max), and NextLong(long min, long max).
  2. Repeatable values. Like I said above, this is huge for being able to still use random values while also being able to reproduce that random value consistently.

Let's see how we can use Random with our HelloWorld tests. We might want to just see if our Hello method can take in a bunch of different strings, but not have to write out hundreds of tests with data.

[Test]
public void Random()  
{
    string randomGreeting = TestContext.CurrentContext.Random.GetString();
    TestContext.WriteLine($"RandomGreeting: {randomGreeting}");

    string greeting = BlogSamples.HelloWorld.Hello(randomGreeting);
    Assert.That(greeting.Equals($"Hello, {randomGreeting}.", StringComparison.Ordinal));
}




There are a lot of useful properties on the CurrentContext that you can use right away in your tests. Others might only have use when used inside of an Engine Extension, which I will cover in a future post.

This concludes the 2 part series on the TestContext. Be on the lookout for more posts on NUnit in the future.

Now I want to know, how do you use the TestContext inside of your tests?


© 2018. All Rights Reserved.

Proudly published with Ghost