This is the first post of a small series in which we will dig into the TestContext class. I originally had this as one post, but part way through it was quite obvious that there was too much information to cover.

Many of you probably aren't familiar with all the builtin properties and methods that you have access to when using NUnit. When NUnit is executing tests, it runs tests in an execution context, which has information about the environment they are running in and on the tests themselves. The TestContext gives you the ability to access that information as well as provide methods to improve your tests.

The TestContext is a singleton that has a lot of useful static properties and methods. You can access the TestContext in any project you are referencing the nunit framework. You can find all the basic information about the TestContext here - TestContext


Test Context - Static Properties

CurrentContext

TestContext.CurrentContext - Gets the context of the currently executing test. This context is created separately for each test before it begins execution. I'll go over the CurrentContext in my next post: Overview of the TestContext in NUnit. Part 2: Using the CurrentContext.

Out

TestContext.Out - Gets a TextWriter used for sending output to the current test result. If using the default IResultWriter, this will place the output into the XML result.

For instance, you might want to save the different greetings that we test.

[Test]
public void Hello()  
{
    string greeting = BlogSamples.HelloWorld.Hello("get-testy");
    TestContext.Out.WriteLine($"Greeting: {greeting}");
    Assert.That(greeting.Equals("Hello, get-testy.", StringComparison.Ordinal));
}

When this test gets executed, this would be the test case node inside the XML result.

<test-case id="0-1286" name="Post" fullname="BlogSamples.NUnit.Hello" methodname="Hello" classname="BlogSamples.NUnit.Hello" runstate="Runnable" seed="969179069" result="Passed" start-time="2017-11-07 19:14:25Z" end-time="2017-11-07 19:14:25Z" duration="0.012080" asserts="1">  
    <output><![CDATA[Greeting: get-testy]]></output>
</test-case>  

This can be useful when you want to track variables that are created inside the test case. When you are debugging failures that occur on build servers, having these types of outputs can help you diagnose failures easier.

Error

TestContext.Error - Gets a TextWriter used for sending error output intended for immediate display. Use this to send messages to the Error output stream.

For instance, you might want to write out an Error that occurred but not stop the execution of the test while still capturing the errors.

for(int i = 0; i < 5; i++)  
{
    try
    {
        //Perform action and suppress exceptions
    }
    catch(System.Exception exception)
    {
        TestContext.Error.WriteLine($"Error: {exception.ToString()}");
    }
}


Progress

TestContext.Progress - Gets a TextWriter used for sending normal (non-error) output intended for immediate display. Use to send information out to the immediate console output. If you have a lot going on in your tests, it's good practice to log what is occurring during your test execution.

For instance, you might want to track actions that are occurring in a loop.

for(int i = 0; i < 5; i++)  
{
    //Some Action
    TestContext.Progress.WriteLine($"Action number {i} has occurred.");
}


TestParameters

Test parameters are going to need their own post to show all the things that you can do. However, I will give a quick overview here just to give a taste of what you can do with the test parameters.

They can be supplied in two different ways: through the nunit console and through the nunit3 visual studio adapter. The static TestParameters property returns an object representing those passed-in parameters. The TestParameters class has the following methods and properties.

properties:
  • Count - The number of parameters.
  • Names - A collection of the names of the supplied parameters.
  • this[string name] - The value of a parameter. In Vb, use Item.
methods:
  • Exists(string name) - Returns true if a parameter of that name exists.
  • Get(string name) - Returns the same value as the indexer.
  • Get<T>(string name, T defaultValue) - Returns the value of the parameter converted from a string to type T or the specified default if the parameter doesn't exist. Throws an exception if conversion fails.

To define the test parameters in visual studio, this would be what a simple *.runsettings file would look like.

<RunSettings>  
    <TestRunParameters>
        <Parameter name="appUrl" value="http://localhost" />
    </TestRunParameters>
</RunSettings>  

Then in our test, we might access the appUrl value like this.

TestContext.Out.WriteLine($"{TestContext.Parameters["appUrl"]}");  



Test Context - Static Methods

Write and WriteLine

Writes text to the current test result and writes text to the current test result but followed by a newline.

Using the TestContext.Write and TestContext.WriteLine is preferred to using System.Console.Write and System.Console.WriteLine due to the System class redirecting the output to standard out. In basic scenarios, you often won't run into a problem with using the System classes, but it's best practice to use the TestContext classes wherever possible.

AddFormatter

If you have custom objects that you are comparing inside constraints, you can use a custom formatter to change the output to make it more human readable.

AddFormatter(ValueFormatter formatter);  
AddFormatter(ValueFormatterFactory formatterFactory);  

We can use either a ValueFormatter or a ValueFormatterFactory, which are delegates, to format our objects.

For instance, we have a basic data transfer object (basicDTO) that we want to compare.

class basicDTO {  
    public string Name { get; set; }
}
private string FormatBasicDTO(basicDTO val)  
{
    return $"Name: {val.Name}";
}
[Test]
public void Post()  
{
    TestContext.AddFormatter<basicDTO>(val => FormatBasicDTO((basicDTO)val));

    basicDTO firstDTO = new basicDTO() { Name = "First DTO" };
    basicDTO secondDTO = new basicDTO() { Name = "Second DTO" };

    Assert.That(firstDTO, Is.EqualTo(secondDTO));
}

When we run this, since we did not override the ToEquals for the comparer on the basicDTO, this test will fail.

With the ValueFormatter, we get:

1) Failed : BlogSamples.NUnit.HelloWorld.Hello  
  Expected: <BlogSamples.NUnit.HelloWorld.Hello+basicDTO>
  But was:  <BlogSamples.NUnit.HelloWorld.Hello+basicDTO>

Without the ValueFormatter, we get:

1) Failed : BlogSamples.NUnit.HelloWorld.Hello  
  Expected: Name: Second DTO
  But was:  Name: First DTO

This is useful only for when you don't have the ability to override the ToString on the object you are comparing. However, best practice would be to just give a useful ToString method and use the built in formatters.

AddTestAttachment

Attaches a file, with optional description, to the current test. The file will be attached to the test result in the xml report.

AddTestAttachment(string filePath, string description = null);  

You might want to do this when:

  • You want to see what state a page is in when a front end test fails. Take a screenshot and add it as an attachment so you can view afterwards.
  • You are building up an in-memory XmlDocument or DataTable and you want to see what the data looks like in a human readable format.

Here would be an example of saving an in-memory XmlDocument to a file and attaching it to the Xml Test Result. This is what you might have inside your test.

//Get the file path for where we want to save the file
string xmlData = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "xmlData.xml");

//Build up your in-memory XmlDocument
XmlDocument doc = new XmlDocument();  
XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);  
XmlElement root = doc.DocumentElement;  
doc.InsertBefore(xmlDeclaration, root);  
XmlElement getTesty = doc.CreateElement(string.Empty, "get-testy", string.Empty);  
doc.AppendChild(getTesty);  
XmlText text1 = doc.CreateTextNode("text");  
getTesty.AppendChild(text1);

//Save your XmlDocument to the actual file on disc
doc.Save(xmlData);

//Attach the file to the Test Result
TestContext.AddTestAttachment(xmlData, "In memory XML Data");  

When you have that block of code execute inside your tests, you will then have an extra file generated and and extra section added to your TestResult.xml.

<!-- attachment node is added to the TestResult.xml now -->  
<attachment>  
   <filePath>E:\SourceCode\BlogSamples\xmlData.xml</filePath>
   <description><![CDATA[In memory XML Data]]></description>
</attachment>  




That covers all of the static properties and methods. I would try playing around with the TestContext and see what you can do to improve your existing tests.

Moving forward, when building new tests, you now have a few more tools in your toolbox for writing quality tests!

© 2018. All Rights Reserved.

Proudly published with Ghost