Originally published on get-testy.com in 2017. Written against NUnit 3.
Updated Oct 2026: fixed a code sample.
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 testName- The name of the test, whether set by the user or generated automaticallyFullName- The fully qualified name of the testClassName- The fully qualified name of the classMethodName- The name of the method representing the test, if anyProperties- AnIPropertyBagof 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
IPropertyBagfor Properties is empty. Since there are no extra attributes putting properties onto this test case, it is empty. - The
IDis 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- AResultStaterepresenting 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- ATestStatuswith four possible values.Label- An optional string value, which can provide sub-categories for each Status.Site- AFailureSitevalue.
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
InconclusiveSkippedPassedWarningFailed
These are the possible values for the FailureSite. This will indicate where the failure occurred in the execution of the test execution.
- FailureSite
TestSetUpTearDownParentChild
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()
{
TestStatus status = TestContext.CurrentContext.Result.Outcome.Status; // using NUnit.Framework.Interfaces;
if (status == 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:
- Uniform methods. These methods extend the behavior on a wide range of types which
System.Randomdoes not include.- IE.
NextLong,NextLong(long max), andNextLong(long min, long max).
- IE.
- 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?