You have an interface and multiple implementers of that interface. You want to test all those implementing classes using the same tests. And you do not want to repeat yourself. That would be … like … Gah!
Using NUnit that is a breeze. But you, you use MSTest, the Microsoft Unit Testing Framework.
So, just for fun, you try porting the example in NUnit post on testing implementers of an interface to the Microsoft Unit Testing Framework.
And run into all manner of problems. But fear not. There is a solution and it actually is pretty easy to boot.
The journey
1. Convert the attributes
For your first try using MSTest, you basically copy the code and rename the attributes. TestFixture
becomes TestClass
and Test
becomes TestMethod
.
As the TestClass
attribute doesn’t support any arguments, you take them off, leaving you with a single, bare TestClass
attribute. That’s a hint by the way that you have no easy way of telling MSTest for which classes to run these tests…
2. Mark the class and methods public
MSTest requires every class that contains test methods and every method to be run as a test to be public
, or it won’t play ball and ignores them regardless of the attributes. So you dutifully add the public
accessibility level to both the class and our single test method.
class PieceOfWood_Fake : IPieceOfWood {} [TestClass] public class IJointer_Contract_MS<T> where T : IJointer, new() { IJointer MakeJointer() { return new T(); } [TestMethod] [TestCategory("Jointer")] public void MakeDoveTailJoint_No_Exceptions_Raised_Should_Return_Non_Null_DoveTailJoint() { var jointer = MakeJointer(); var DoveTailJoint = jointer.MakeDoveTailJoint(new PieceOfWood_Fake(), new PieceOfWood_Fake()); Assert.IsNotNull(DoveTailJoint); } }
That comes back to bite you in the behind though with the compiler complaining:
Inconsistent accessibility: constraint type 'ActualProject.InterfaceContract.IJointer' is less accessible than 'TestProject.InterfaceContract.IJointer_Contract_MS'
Duizend bommen en granaten![1]
You really don’t want to make the code in your actual project any more accessible from the outside than absolutely strictly necessary. Catering to unit tests is never a good reason to increase the accessibility level and while IJointer
may be an interface, it still is an internal implementation detail of the actual project and should remain so.
Introduce a public base interface?
Could you get around this by introducing a public “test” interface that IJointer
can derive from? It does mean adding a superfluous interface to the actual project, but you’ll take that over increasing IJointer
‘s accessibility level.
public interface IAny {} interface IJointer : IAny { IDoveTailJoint MakeDoveTailJoint(IPieceOfWood woodTails, IPieceOfWood woodPins); }
And you can now declare the testclass using the public IAny
, which keeps the compiler happy.
[TestClass] public class IJointer_Contract_MS<T> where T : IAny, new() { IJointer MakeJointer() { return new T() as IJointer; } }
3. Tell MSTest for which classes to run the tests
Now that you have a generic test class that can be used to test all classes implementing the IJointer
interface, you need a way to tell MSTest for which classes to do so. That would be both the FirstJointer
and SecondJointer
classes from the NUnit example.
Deriving a specific test class from the generic test class?
As the TestClass
attribute does not support constructor arguments like NUnit’s TestFixture
attribute, that’s out, so … what about deriving a specific class from the generic one?
[TestClass] public class IJointer_FirstJointer : IJointer_Contract_MS<FirstJointer> { }
Looks good, but… Nope.
That runs into the exact same problem with regard to accessibility levels as you faced with the IJointer
interface.
Inconsistent accessibility: base class 'TestProject.InterfaceContract.IJointer_Contract_MS' is less accessible than class 'TestProject.InterfaceContract.IJointer_FirstJointer'
Introduce a public base class?
You could create a public abstract AnyJointer
class that every IJointer
implementer could derive from.
public abstract class AnyJointer : IJointer { public abstract IDoveTailJoint MakeDoveTailJoint(IPieceOfWood woodTails, IPieceOfWood woodPins); } class FirstJointer : AnyJointer { public override IDoveTailJoint MakeDoveTailJoint(IPieceOfWood woodTails, IPieceOfWood woodPins) { return new FirstDoveTailJoint(); } }
But, really, could derive from? Doing it like this means “could” becomes “should”. And that totally defeats (one of) the object(s) of using interfaces! You would no longer be free to make any class implement IJointer
. Every class implementing IJointer
would now have to fit into a very specific class hierarchy.
And it is not even going to work, because
[TestClass] public class IJointer_FirstJointer : IJointer_Contract_MS<AnyJointer> { }
would make
public class IJointer_Contract_MS<T> where T : IAny, new() { IJointer MakeJointer() { return new T() as IJointer; } }
instantiate the AnyJointer
class, which, besides being impossible as it is abstract
– easily overcome by not marking it such, is not what you need. You need instances of the FirstJointer
and SecondJointer
classes to test againt the IJointer
interface contract.
Mark MakeJointer virtual?
To make the IJointer_FirstJointer
test class return a FirstJointer
instance you can make MakeJointer
virtual in the contract class and override it.
public class IJointer_Contract_MS<T> where T : IAny, new() { internal virtual IJointer MakeJointer() { return null; } }
And, better, mark it abstract so derived test classes are required to override it.
public abstract class IJointer_Contract_MS<T> where T : IAny, new() { internal abstract IJointer MakeJointer(); }
Notice that this requires you to mark the contract class abstract as well or you will get compiler errors.
But, but, but… that totally defeats the object of defining a generic test class!
Back to square one then?
In a sense, yes. And in this case, that is a good place to be.
If you ditch the generic test class, then you can also ditch everything you did to work around MSTest’s requirement that every test class and every test method should be public.
So you scrap IAny
and AnyJointer
in the actual project, which puts everything in there back to having an internal accessibility level only. Yay!
The solution
When you are using the Microsoft Unit Testing Framework and you want to leave everything in your actual project with no more than the internal accessibility level it should have, then to test every class implementing the IJointer
interface with the same tests, you need to
- use an abstract test class that defines the tests to be run, and
- define specific test classes for each class implementing the interface.
1. Define an abstract base test class
[TestClass] public abstract class IJointer_Contract_MS { internal abstract IJointer MakeJointer(); [TestMethod] [TestCategory("Jointer")] public void MakeDoveTailJoint_No_Exceptions_Raised_Should_Return_Non_Null_DoveTailJoint() { var jointer = MakeJointer(); var DoveTailJoint = jointer.MakeDoveTailJoint(new PieceOfWood_Fake(), new PieceOfWood_Fake()); Assert.IsNotNull(DoveTailJoint); } }
2. Derive a specific test class per implementer
To make MSTest run the contract tests for each class implementing the IJointer
interface, you have to derive a specific test class for each of them manually and provide each with its own implementation for the MakeJointer
method.
[TestClass] public class IJointer_FirstJointer : IJointer_Contract_MS { internal override IJointer MakeJointer() { return new FirstJointer(); } } [TestClass] public class IJointer_SecondJointer : IJointer_Contract_MS { internal override IJointer MakeJointer() { return new SecondJointer(); } }
3. See the classes being tested against the same contract
All in all a bit more work than using a generic test class and the TestFixture
attribute as you can with NUnit, but not too bad at all.
That’s it. Enjoy!
What code had you wondering how to test it? I’d love hearing from you by email or in the comments below. I read everything and will try to help where and as best I can.
Notes
[1] Dutch version of a Captain Haddock expletive. Literal translation: “Thousand bombs and grenades!”. Closest English version is probably the “Ten thousand thundering typhoons!” one.
Leave a Reply