Mark Struzinski
Mark Struzinski

Reputation: 33471

NUnit Conditional Teardown?

Is there a way to do a conditional TearDown in NUnit?

I have a TestFixture which has a need to run cleanup code for just a few tests, and I don't really want to:

  1. Run the TearDown method on every test
  2. Create a private helper method and call it from the tests requiring cleanup if I can avoid it

Upvotes: 6

Views: 3997

Answers (4)

suitendaal
suitendaal

Reputation: 193

I have solved this using the name of the test:

namespace TestProject
{
    public class TestClass
    {
        // Test without TearDown
        [Test]
        public void Test1()
        {
            Assert.Pass("Test1 passed");
        }

        // Test with TearDown
        [Test]
        public void Test2()
        {
            Assert.Pass("Test2 passed");
        }

        [TearDown]
        public void TearDown()
        {
            // Execute only after Test2
            if (TestContext.CurrentContext.Test.Name.Equals(nameof(this.Test2)))
            {
                // Execute Test2 TearDown...
            }
        }
    }
}

Or if you want to use the full name of Test2 (TestProject.TestClass.Test2) you can replace the line

if (TestContext.CurrentContext.Test.Name.Equals(nameof(this.Test2)))

by

if (TestContext.CurrentContext.Test.FullName.Equals(typeof(TestClass).FullName + "." nameof(this.Test2)))

Upvotes: 1

shatulsky
shatulsky

Reputation: 336

Extend all you classes with test from BaseTest

    public class BaseTest
    {
        [SetUp]
        public void BeforeTest()
        {
            GetService<NUnitHooksController>().ExecuteBeforeTestHooks(this);
        }

        [TearDown]
        public void AfterTest()
        {
            GetService<NUnitHooksController>().ExecuteAfterTestHooks(this);
        }
    }

Use AfterTest and BeforeTest hooks. Works both with and without category.

    public class ExampleTest : BaseTest
    {
        [Test, Category("asdasd")]
        public void Test01()
        {
           ...
        }

        [AfterTest("asdasd")]
        public void ExampleHook()
        {
           ...
        }

    }
    public class NUnitHooksController
    {
        private readonly ILogger _log;

        public NUnitHooksController(ILogger log)
        {
            _log = log;
        }

        public void ExecuteBeforeTestHooks(object testClass)
        {
            ExecuteHooks(testClass, typeof(BeforeTestAttribute));
        }

        public void ExecuteAfterTestHooks(object testClass)
        {
            ExecuteHooks(testClass, typeof(AfterTestAttribute));
        }

        private MethodInfo[] GetHookMethods(object currentTestClass, Type attributeType)
        {
            return currentTestClass
                .GetType()
                .GetMethods()
                .Where(m => m.GetCustomAttributes(attributeType, false).Length > 0)
                .ToArray();
        }

        private void ExecuteHooks(object testClass, Type requiredAttributeType)
        {
            var hooks = GetHookMethods(testClass, requiredAttributeType);
            var testCategories = GetTestCategories();

            foreach (var hook in hooks)
            {
                var allAttributes = hook.GetCustomAttributes(requiredAttributeType, true);
                foreach (var attribute in allAttributes)
                {
                    if (!attribute.GetType().IsEquivalentTo(requiredAttributeType))
                    {
                        continue;
                    }

                    var hookCategories = GetCategoriesFromAttribute(attribute);

                    // if we do not have specific category on hook
                    // or we have at least one same category on hook and test
                    if (!hookCategories.Any() || hookCategories.Intersect(testCategories).Any())
                    {
                        ExecuteHookMethod(testClass, hook);
                    }
                }
            }
        }

        private object[] GetTestCategories()
        {
            return TestContext.CurrentContext.Test.Properties["Category"].ToArray();
        }

        private void ExecuteHookMethod(object testClass, MethodInfo method)
        {
            var hookName = method.Name;
            _log.Information($"Executing - '{hookName}' hook");

            try
            {
                method.Invoke(testClass, Array.Empty<object>());
            }
            catch (Exception e)
            {
                _log.Error($"Executing of - '{hookName}' hook failed - {e}");
            }
        }

        private string[] GetCategoriesFromAttribute(object attribute)
        {
            if (attribute is BeforeTestAttribute beforeTestAttribute)
            {
                return beforeTestAttribute.Categories;
            }

            if (attribute is AfterTestAttribute afterTestAttribute)
            {
                return afterTestAttribute.Categories;
            }

            throw new ArgumentException($"{attribute.GetType().FullName} - does not have categories");
        }
    }

Upvotes: 0

Skywise
Skywise

Reputation: 429

You can have the main TearDown in a base class:

[TearDown]
public virtual void TearDown()
{
  // Tear down things here
}

and then override it in the class where you have the tests that should not run the tear down code:

[TearDown]
public override void TearDown()
{
  // By not calling base.TearDown() here you avoid tearing down
}

Upvotes: 0

AdaTheDev
AdaTheDev

Reputation: 147224

There isn't unfortunately.

Can you not do the cleanup in the [TestFixtureTearDown] instead, so once all the tests have finished? I guess that depends on whether the cleanup has to be done before the next test runs.

Alternatively, put those tests that require a cleanup in another class/TextFixture together, away from the other tests. Then you can use a TearDown in there which doesn't need to be conditional.

Edit: One thing I've just thought of, which could be done to achieve the aim though probably isn't actually worth it for this particular need, is that you can extend NUnit - create your own custom attributes which you could handle however you wanted. This is mentioned here. Like I say, I don't think really you should go down that route for this, but is useful to know none-the-less

Upvotes: 2

Related Questions