I want to have parametrized tests in NUnit for tests running in two different scenarios. Most/Some tests are the same, but some are not. So I thought, I want to have a base class for running common tests in two scenarios.
Here is the full MVE:
using FluentAssertions;
using NUnit.Framework;
public class SystemUnderTest
{
public bool? CrucialSettingsForSut { get; }
public SystemUnderTest(bool crucialSettingsForSut)
{
// does something with crucialSettingsForSut that inheritly changes the logic, this is just an example
this.CrucialSettingsForSut = crucialSettingsForSut;
}
}
public class ConstructorTest
{
public class BaseTestClass
{
protected readonly SystemUnderTest sut;
public BaseTestClass(bool crucialSettingsForSut)
{
this.sut = new SystemUnderTest(crucialSettingsForSut);
}
[Test]
public void TestApplicableToBothCases()
{
this.sut.CrucialSettingsForSut.Should().NotBeNull();
}
}
[TestFixture]
public class ActualTestFixtureFalse : BaseTestClass
{
public ActualTestFixtureFalse() : base(false) { }
[Test]
public void TestOnlyApplyingToFalse()
{
this.sut.CrucialSettingsForSut.Should().BeFalse();
}
}
[TestFixture]
public class ActualTestFixtureTrue : BaseTestClass
{
public ActualTestFixtureTrue() : base(true) { }
[Test]
public void TestOnlyApplyingToTrue()
{
this.sut.CrucialSettingsForSut.Should().BeTrue();
}
}
}
This is a simplified example, of course the real code is more complex.
The important thing is that TestOnlyApplyingToTrue
and TestOnlyApplyingToFalse
are contradictory, i.e. I cannot place them in the same class and have both tests run in a TestFixture
with the parameter for the constructor.
Problem
Unfortunately NUnit tries to instantiate BaseTestClass
even though I do not actually want this. After all, this just contains the set of "base tests":
This results in this error for TestApplicableToBothCases
:
OneTimeSetUp: No suitable constructor was found
Exception doesn't have a stacktrace
-----
No suitable constructor was found
Exception doesn't have a stacktrace
Tries
I saw No suitable constructor was found in NUnit Parameterised tests, but in my case BaseTestClass
should be there and has a purpose, as explained. I cannot solve this with TestFixture
.
An alternative may be to have a custom Assert.Ignore
in TestOnlyApplyingToTrue
and TestOnlyApplyingToFalse
only triggered if the respective other test case is run, but this is IMHO ugly code design and causes an "ignored test" entry.
The way with inheritage I used here makes much more sense.
Also, obviously this hardly scales as I not have only one test for each scenario as in this simplified example.
Design patterns
You may wonder about that design, but IMHO it is good for simple cases. Actually, it is featured as abstract test infrastructure class pattern in The Art of Unit Testing Second Edition (with examples in C#) by Roy Osherove. (ISBN: 9781617290893)
For what it matters: At least in the version I have, they actually have the same error in there! BaseTestsClass
(in Listing 7.4, page 139) is not abstract. (I wrote this after knowing the solution.)
Just it does not matter, because no actual test is included in the (non-abstract) base class there.
Listing 7.6 etc. then uses a real abstract base class as the solution for this one and as far as I read also includes a test in the base class, then.
Question
So how can I solve this in the most elegant way?
I want to have parametrized tests in NUnit for tests running in two different scenarios. Most/Some tests are the same, but some are not. So I thought, I want to have a base class for running common tests in two scenarios.
Here is the full MVE:
using FluentAssertions;
using NUnit.Framework;
public class SystemUnderTest
{
public bool? CrucialSettingsForSut { get; }
public SystemUnderTest(bool crucialSettingsForSut)
{
// does something with crucialSettingsForSut that inheritly changes the logic, this is just an example
this.CrucialSettingsForSut = crucialSettingsForSut;
}
}
public class ConstructorTest
{
public class BaseTestClass
{
protected readonly SystemUnderTest sut;
public BaseTestClass(bool crucialSettingsForSut)
{
this.sut = new SystemUnderTest(crucialSettingsForSut);
}
[Test]
public void TestApplicableToBothCases()
{
this.sut.CrucialSettingsForSut.Should().NotBeNull();
}
}
[TestFixture]
public class ActualTestFixtureFalse : BaseTestClass
{
public ActualTestFixtureFalse() : base(false) { }
[Test]
public void TestOnlyApplyingToFalse()
{
this.sut.CrucialSettingsForSut.Should().BeFalse();
}
}
[TestFixture]
public class ActualTestFixtureTrue : BaseTestClass
{
public ActualTestFixtureTrue() : base(true) { }
[Test]
public void TestOnlyApplyingToTrue()
{
this.sut.CrucialSettingsForSut.Should().BeTrue();
}
}
}
This is a simplified example, of course the real code is more complex.
The important thing is that TestOnlyApplyingToTrue
and TestOnlyApplyingToFalse
are contradictory, i.e. I cannot place them in the same class and have both tests run in a TestFixture
with the parameter for the constructor.
Problem
Unfortunately NUnit tries to instantiate BaseTestClass
even though I do not actually want this. After all, this just contains the set of "base tests":
This results in this error for TestApplicableToBothCases
:
OneTimeSetUp: No suitable constructor was found
Exception doesn't have a stacktrace
-----
No suitable constructor was found
Exception doesn't have a stacktrace
Tries
I saw No suitable constructor was found in NUnit Parameterised tests, but in my case BaseTestClass
should be there and has a purpose, as explained. I cannot solve this with TestFixture
.
An alternative may be to have a custom Assert.Ignore
in TestOnlyApplyingToTrue
and TestOnlyApplyingToFalse
only triggered if the respective other test case is run, but this is IMHO ugly code design and causes an "ignored test" entry.
The way with inheritage I used here makes much more sense.
Also, obviously this hardly scales as I not have only one test for each scenario as in this simplified example.
Design patterns
You may wonder about that design, but IMHO it is good for simple cases. Actually, it is featured as abstract test infrastructure class pattern in The Art of Unit Testing Second Edition (with examples in C#) by Roy Osherove. (ISBN: 9781617290893)
For what it matters: At least in the version I have, they actually have the same error in there! BaseTestsClass
(in Listing 7.4, page 139) is not abstract. (I wrote this after knowing the solution.)
Just it does not matter, because no actual test is included in the (non-abstract) base class there.
Listing 7.6 etc. then uses a real abstract base class as the solution for this one and as far as I read also includes a test in the base class, then.
Question
So how can I solve this in the most elegant way?
Share Improve this question edited Feb 6 at 17:25 rklec asked Feb 6 at 13:27 rklecrklec 3191 silver badge19 bronze badges3 Answers
Reset to default 2@Klaus gave you the write answer for your case, but I wanted to spell it out in more detail beyond what's possible in a comment.
When you mark a non-abstact class as a test fixture either explicitly with [TestFixture]
or implicitly by including tests in the class, you are telling the NUnit framework to instantiate it and run those tests. Of course, that's impossible without a proper constructor, which gives rise to the error.
OTOH, an abstract class is never treated that way. It really isn't a TestFixture
, merely a base class for your real test fixtures. "Abstract TestFixture" is the standard pattern for including tests in multiple fixtures. It works like this...
- Create an abstract class to use as a base. Ensure that it has whatever constructors you need for your purpose. Don't include
[TestFixture]
on the base. If you do, it will still work, but the attribute tends to confuse those who read your code. Include[Test]
,[TestCase]
, etc. methods in the base class. - Create as many derived classes as you need. Make sure that each one has whatever constructors it needs and that each one calls the proper constructor in the base class. Of course, if the base has only a default constructor, this happens automatically.
Sometimes, folks ask "But couldn't NUnit just figure all that out instead of giving an error?" Maybe... but we designed the framework with the philosophy that it would attempt to do whatever you told it and give an error if that was not possible. Nowadays, it's more common to use analyzers that give you warnings when you do something suspicious, like inheriting from a non-abstract test fixture. The latest NUnit.Analyzers
might have given you some kind of warning in this case.
UPDATE: As @rklec commented, there is no rule for this in NUnit.Analyzers
. I created this issue to add a rule.
If you stick to your design, you can make the base test class abstract
. This will avoid that the NUnit test engine or test runners try to instantiate the class.
I tested with MsTest and Resharper runners and it seems to work fine.
As with any other class, it's usually better to favour composition over inheritance, so instead of deriving from your class containing the common stuff, just use it:
public class ConstructorTest
{
public class BaseTestClass
{
private readonly SystemUnderTest sut;
public BaseTestClass(bool crucialSettingsForSut)
{
this.sut = new SystemUnderTest(crucialSettingsForSut);
}
public void TestApplicableToBothCases()
{
this.sut.CrucialSettingsForSut.Should().NotBeNull();
}
}
[TestFixture]
public class ActualTestFixtureFalse
{
BaseTestClass baseTest = new BaseTestClass(false);
[Test]
public void TestOnlyApplyingToFalse()
{
this.sut.CrucialSettingsForSut.Should().BeFalse();
}
[Test]
public void TestApplicableToBothCases()
{
this.baseTest.TestApplicableToBothCases(); // call the "base"-class function here
}
}
[TestFixture]
public class ActualTestFixtureTrue
{
BaseTestClass baseTest = new BaseTestClass(true);
[Test]
public void TestOnlyApplyingToTrue()
{
this.sut.CrucialSettingsForSut.Should().BeTrue();
}
[Test]
public void TestApplicableToBothCases()
{
this.baseTest.TestApplicableToBothCases(); // call the "base"-class function here
}
}
}
Of course you're doubling some lines of code. But this saves you headaches if your BaseTestClass
sometime changes. E.g. when someone adds a new function to it that shouldn't actually be used by all tests, you'd need some way to disable that function - and kill the developer who introduced that new function.