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
- AnIPropertyBag
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
- AResultState
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
- ATestStatus
with four possible values.Label
- An optional string value, which can provide sub-categories for each Status.Site
- AFailureSite
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:
- Uniform methods. These methods extend the behavior on a wide range of types which
System.Random
does 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?