Friday, August 14, 2020

Detecting if code is being run as part of a unit test

There are times when you need to know if your code is being run as part of a unit test. For example, you might want your unit test's setup to call methods that regular code is not allowed to call. That method could raise a NotImplemented exception if it is not called from a unit test. How can we do this?

In the following example I am going to setup a class, a console app that calls that class, unit tests for nUnit, xUnit, and MSTest all in the same project, and a class method that determines if it is being called from one of the unit tests.

Start a new C# console app using .Net Core in Visual Studio 2019. Call it DetectUnitTest. Add a class called TargetClass that looks like this.


namespace DetectUnitTest
{
    public static class TargetClass
    {
        public static int Add(int a, int b)
        {
            return a + b;
        }
    }
}

Now we can write Program.cs


using System;

namespace DetectUnitTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(string.Format("{0} + {1} = {2}", 2,2,TargetClass.Add(2,2)));
        }
    }
}

Run the program and see the fruits of your labor.


Let's add the unit tests. Add a new class library project called UnitTests.

To Add MSTest, use NuGet to add these three packages

  • Microsoft.Net.Test.Sdk
  • MSTest.TestAdapter
  • MSTest.TestFramework

To add nUnit, use NuGet to add these two packages

  • NUnit
  • NUnit3.TestAdapter

To Add xUnit, use NuGet to add these two packages

  • xunit
  • xunit.runner.visualstudio


Also add a reference to the DetectUnitTest project so we can test it.

Add classes to the UnitTests project called MSUnitTests, NUnitTests, and XUnitTests. We will add a simple test to each one.

MSUnitTests

using DetectUnitTest;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests
{
    [TestClass]
    public class MSUnitTests
    {
        [TestMethod]
        public void AddTest()
        {
            Assert.AreEqual(4, TargetClass.Add(2, 2));
        }
    }
}


NUnitTests

using DetectUnitTest;
using NUnit.Framework;

namespace UnitTests
{
    class NUnitTests
    {
        [Test]
        public void AddTest()
        {
            Assert.AreEqual(4, TargetClass.Add(2, 2));
        }
    }
}


XUnitTests

using DetectUnitTest;
using Xunit;

namespace UnitTests
{
    public class XUnitTests
    {
        [Fact]
        public void AddTest()
        {
            Assert.Equal(4, TargetClass.Add(2, 2));
        }
    }
}

We can run these unit tests now. From the menu, click on Test -> Test Explorer. Then click the "Run All Tests" button (the left most green arrow). All the tests will pass.


Now we will write the method that detects if the code is being run within a unit test. Add a class to DetectUnitTest called DetectUnitTest (not very imaginative). The code caches the result which is determined by looking through the call stack to see if any of the methods are decorated with specific attributes. The idea is taken from StackOverflow, but I can't find the original post to attribute it. 

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace DetectUnitTest
{
    public static class DetectUnitTest
    {
        public static readonly List<string> UnitTestAttributes = new List<string>
        {
            "Xunit.FactAttribute",
            "Xunit.TheoryAttribute",
            "Nunit.TestAttribute",
            "Nunit.TheoryAttribute",
            "Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute",
            "Microsoft.VisualStudio.TestTools.UnitTesting.DataTestMethodAttribute"
        };

        private static bool? _IsRunningInUnitTest = null;
        public static bool IsRunningInUnitTest
        {
            get
            {
                if (!_IsRunningInUnitTest.HasValue)
                {
                    _IsRunningInUnitTest = false;
                    foreach (var f in new StackTrace().GetFrames())
                    {
                        if (f.GetMethod().GetCustomAttributes(false).Any(x => UnitTestAttributes.Contains(x.GetType().FullName)))
                        {
                            _IsRunningInUnitTest = true;
                            break;
                        }
                    }
                }

                return _IsRunningInUnitTest.Value;
            }
        }
    }
}

Let's consume it from the console app where the value returned should be false. Modify Program.cs to add this line.

using System;

namespace DetectUnitTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(string.Format("{0} + {1} = {2}", 2,2,TargetClass.Add(2,2)));
            Console.WriteLine(string.Format("Running in unit test = {0}", DetectUnitTest.IsRunningInUnitTest));
        }
    }
}

When we run the application now, the output looks like this.


Now let's see if it can detect when it's being called from the unit test classes. Add these tests to the Unit Tests.

MSUnitTests


        [DataTestMethod]
        [DataRow(true)]
        public void IsRunningUnitTest(bool Expected)
        {
            Assert.AreEqual(Expected, DetectUnitTest.DetectUnitTest.IsRunningInUnitTest);
        }

NUnitTests


        [Datapoint] bool IsTest = true;
        [Theory]
        public void IsRunningUnitTest(bool Expected)
        {
            Assert.AreEqual(Expected, DetectUnitTest.DetectUnitTest.IsRunningInUnitTest);
        }

xUnitTests


        [Theory]
        [InlineData(true)]
        public void IsRunningUnitTest(bool Expected)
        {
            Assert.Equal(Expected, DetectUnitTest.DetectUnitTest.IsRunningInUnitTest);
        }


If you run the tests in TestExplorer now you will see all these tests are passing, so we were able to detect when we were running inside a test.


You can see the test explorer was able to homologate the results from multiple test frameworks. Take a moment to understand how impressive that is.

No comments:

Post a Comment