0
votes

I have to write some integration tests with NUnit that should test HTTP endpoints. This means that URL should be shared across all test methods.

[TestFixture("http://my.endpoint.com")]
public class TestSuiteOne
{
   [TestCase(10, 20, Expected = 30)]
   [TestCase(20, 30, Expected = 50)]
   public int TestA(int a, int b, HttpResponseMessage sut)
   {
        // AAA.
   }

   [TestCase("XXX", "YYY", Expected = "ABC")]
   public int TestA(string a, string b, HttpResponseMessage sut)
   {
        // AAA.
   }
}

To get SUT value on each test run that comes from test suite attribute I know two options:

Option A (read SUT from class property)


public abstract class TestSuiteBase
{
    protected(string endpoint)
    {
        Sut = endpoint;
    }

    protected HttpResponseMessage Sut { get; }
}

[TestFixture("http://my.endpoint.com")]
public class TestSuiteOne : TestSuiteBase
{
   public TestSuiteOne(string endpoint) : base(endpoint)
   {
   }

   [TestCase(10, 20, Expected = 30)]
   [TestCase(20, 30, Expected = 50)]
   public int TestA(int a, int b)
   {
        // act (read content/response code/headers/etc); Making a call I do not consider as act.
        var actual = Sut.DoSomething();

        // assert
   }

   [TestCase("XXX", "YYY", Expected = "ABC")]
   public int TestA(string a, string b)
   {
        // act (read content/response code/headers/etc); Making a call I do not consider as act.
        var actual = Sut.DoSomething();

        // assert
   }
}

Option B (intercept test method call)

public class MyTestCase: TestCaseAttribute
{
    public MyTestCase(params object[] args) : base(Resize(args))
    {
    }

    // I do resize before method call because VS Test Adapters discover tests to show a list in the test explorer
    private static object[] Resize(params object[] args)
    {
        Array.Resize(ref args, args.Length + 1);
        args[args.Length - 1] = "{response}";

        return args;
    }
}

public abstract class TestSuiteBase
{
   [SetUp]
   public void OnBeforeTestRun()
   {
       var ctx = TestContext.CurrentContext;

       var args = ctx.Test.Arguments;

       // Making a call I do not consider as act.
       args[args.Length - 1] = MakeCallAndGetHttpResponseMessage(args);
   }
}

[TestFixture("http://my.endpoint.com")]
public class TestSuiteOne : TestSuiteBase
{
   [MyTestCase(10, 20, Expected = 30)]
   [MyTestCase(20, 30, Expected = 50)]
   public int TestA(int a, int b, HttpResponseMessage sut)
   {
        // act (read content/response code/headers/etc); Making a call I do not consider as act.
        var actual = sut.DoSomething();

        // assert
   }

   [MyTestCase("XXX", "YYY", Expected = "ABC")]
   public int TestA(string a, string b, HttpResponseMessage sut)
   {
        // AAA.
   }
}

Is there any more convenient way how to combine value that comes from TestFixture with TestCase?

1
Option 1 looks good and maintanable - Pavel Anikhouski
The first options looks like "shared object"; Only guys who have experience with xUnit and NUnit may be confused due to the difference in life cycle that I'm trying to avoid. xUnit creates an instance of the class per test run but NUnit works differently. Guys how have experience only with xUnit they may read that property as expected but at the same time NUnit guys may read it as shared state. Also using that object as a property will not work if test runs in parallel - Dmitriy Sosunov
You make create per-fixture setup method and initialize the sut inside setup - Pavel Anikhouski
Not sure that I understand what do you mean, sorry Can you clarify? - Dmitriy Sosunov

1 Answers

0
votes

Since you placed the endpoint string on the TestFixtureAttribute, I will assume

  1. That you want to share it with all the test cases in the fixture.
  2. That you do not want to share it with any other fixtures.

The only thing missing in your example is a constructor that will accept the argument you are providing. Just add something like...

public TestSuiteOne(string url)
{
     Sut = endpoint;
}

So long as you make Sut a readonly property or field, none of your testcases will be able to change it, whether they are run sequentially or in parallel.

Both the options you provide seem overly complex to me, at least insofar as you have explained the problem. The first one is closest to this answer.

Of course, it's entirely possible that you might do other things with the web site, which would cause parallel (or even sequential) tests to interfere with one another. Don't do that. :-) Seriously, sharing a driver is entirely different from sharing a string, but that's a different question.

IMO, by the way, you and your team should get used to one test framework or another. The lifecycle issue you mention is not the only subtle difference between NUnit and xUnit!