Hierarchical "OneTimeSetUp" methods

Viewed 405

Ok, I've got some nunit tests I'm writing to test an API. Any time I need to run these tests, I first need to login to the api to obtain a token. To start with, that's how I've written my OneTimeSetUp.

So, OneTimeSetUp is called, I log in, a shared field stores the token, each test is called a tests a different endpoint on api.

Now the problem. We've decided that we want to have individual tests for individual fields on the response, so that we can see what exactly is (and isn't failing) if something is wrong. So, we split out each endpoint into it's own test.

Now, OneTimeSetUp is called, it logs in, and calls the endpoint, stores the result, and all the tests fire, testing their little bit.

The problem is, logging in takes time, and there is no logical reason why all the separate tests couldn't just use the same login details. Is there any way of further sub-dividing tests/ adding extra levels of test? It would be great if we could get a test result that looks like this

ApiTests <--- shared sign-in at this level - Endpoint 1 <--- call the endpoint at this level - Field 1 \ - Field 2 --- individual test results here - Field 3 / - Endpoint 2 <--- call the endpoint at this level - Field a \ - Field b --- individual test results here - Field c /

2 Answers

You can group your test classes into the same namespaces and then add an additional class that is marked with the SetupFixture attribute. This will run the initialization code only once per namespace. (Not to be confused with the "TestFixtureSetUp" attribute, which is marked obsolete since NUnit v3. Thanks Charlie for your comment, I initially mixed it up.)

https://github.com/nunit/docs/wiki/SetUpFixture-Attribute

Code sample (as always, you are free to put each class into a separate code file):

using System.Diagnostics;
using NUnit.Framework;

namespace Test
{
    [SetUpFixture]
    public class SharedActions
    {
        [OneTimeSetUp]
        public void SharedSignIn()
        {
            Debug.WriteLine("Signed in.");
        }

        [OneTimeTearDown]
        public void SharedSignOut()
        {
            Debug.WriteLine("Signed out.");
        }
    }

    [TestFixture]
    public class FirstEndpointTests
    {
        [Test]
        public void FirstEndpointTest()
        {
            Debug.WriteLine("Test for Endpoint A");
        }
    }

    [TestFixture]
    public class SecondEndpointTests
    {
        [Test]
        public void SecondEndpointTest()
        {
            Debug.WriteLine("Test for Endpoint B");
        }
    }
}

When you "debug all" tests, the following output will appear in the debug window:

Signed in. Test for Endpoint A Test for Endpoint B Signed out.

Here is one possible way of achieving this.

If you have a common base class (as it sounds from your description), you can create a protected lazy to get your token as per the example below

public class ApiTestsBase
{
    protected static Lazy<string> TokenLazy = new Lazy<string>(() =>
                                                             {
                                                                 // Log in and get your API token
                                                                 Console.WriteLine("Logging into API to get token. You should only see this message on the first test that runs"); 
                                                                 return "DEADBEEF";
                                                             });

}

[TestFixture]
public class EndpointATests : ApiTestsBase
{
    private string GetResultFromEndPoint()
    {
        // Call endpoint with token from TokenLazy.Value
        Console.WriteLine($"Calling EndpointA with token {TokenLazy.Value}");
        return "PayloadA";
    }

    [Test]
    public void Test1()
    {
        var payload = this.GetResultFromEndPoint();
        // Assert things about payload
    }

}

[TestFixture]
public class EndpointBTests : ApiTestsBase
{
    private string GetResultFromEndPoint()
    {
        // Call endpoint with token from TokenLazy.Value
        Console.WriteLine($"Calling EndpointB with token {TokenLazy.Value}");
        return "PayloadB";
    }

    [Test]
    public void Test1()
    {
        var payload = this.GetResultFromEndPoint();
        // Assert things about payload
    }

}

Now I am using string types, but you can use whatever request, response and token types are relevant to your situation. I suspect that you could also with a bit of creativity move the GetResultFromEndPoint call to the base class and use abstract methods or properties to fill in the endpoint specific detail, but you have not shared enough code for me to try that.

The magic is in the static keyword which means you will only have one instance per app domain. The Lazy simply defers creation until its first reference. It gets a little more complex if your test cases run for a long time because you will need to deal with token renewal, but it can still be achieved in a similar way using a singleton class that periodically re authenticates if token age > x. A singleton object can also be used in place of the static in the above example if you do not have a common base class for your fixtures.

Related