How do I test an interface? Should I even do that?

Every so often this comes up: I have an interface. It has methods. I should unit test everything. So I should test this interface. But how? It doesn’t have an implementation…?!

Right…

Well…

Hmm…

No. Of course you don’t test the interface. You can’t. It has no implementation.

But…

You do want to test each and every class that implements this interface.

To check that any class that implements the interface meets the expectations of the clients of that interface.

Okay…

So how do you go about that?

After all, you want to keep your tests DRY. So you certainly don’t want to write all the tests with regard to the interface all over again for each new implementer of the interface.

It depends…

Yeah I know. Sounds like a cop out. But in this case it really depends on the unit test framework you use and what it enables you to do.

What holds true regardless of which language and framework you use is that you create a single test class to hold all the tests that you want to run for all implementers of your interface. And you code all the methods in that test class using a reference to the interface (instead of a reference to a specific class).

Sounds a bit complicated, but it is fairly straightforward.

Say you have an interface ISomeInterface with a single method SomeMethod.

  ISomeInterface = interface(IInterface)
    function SomeMethod(const aInput: string): Boolean;
  end;

And you want to ensure that any implementer of ISomeInterface returns false when it is passed an empty string. Somewhat like:

  CheckEquals(FInterfaceReference.SomeMethod(''), False);

NUnit

Using C# and NUnit you would just write a single generic class, give it the test methods you need to ensure implementers meet the desired behavior and then decorate it with [TestFixture(typeof(SomeImplementation))] attributes. One attribute for each implementing class. That’s all.

[TestFixture(typeof(MyImplementerOfSomeInterface))] 
[TestFixture(typeof(MyOtherImplementerOfSomeInterface))] 
public class TestsOfISomeInterface<T> where T : ISomeInterface, new() 
{ 
    ISomeInterface impl; 

    [SetUp] 
    public void CreateISomeInterfaceImplementation() 
    { 
        impl = new T(); 
    } 

    // Use impl in your tests 
} 

DUnitX

In Delphi with DUnitX I expect you could do much the same thing as DUnitX is modelled on NUnit.

(I haven’t had the pleasure of using DUnitX yet. Please chime in with a comment if you know for sure.)

DUnit on generics

If you are using a Delphi version which supports generics, you can do much the same thing using DUnit. You start by declaring a generic test class for the tests on implementers of ISomeInterface.

type
  TSomeInterfaceContractTests<T: TInterfacedObject, constructor> = class(TTestCase)
  strict private
    FInterfaceUnderTest: ISomeInterface;
  protected
    procedure SetUp; override;
  published
    procedure SomeMethod_Input_EmptyString_ShouldReturn_False;
  end;

procedure TSomeInterfaceContractTests<T>.SetUp;
begin
  inherited;
  Supports(T.Create, ISomeInterface, FInterfaceUnderTest);
  Assert(Assigned(FInterfaceUnderTest), 'Generic type does not support ISomeInterface');
end;

procedure TSomeInterfaceContractTests<T>.SomeMethod_Input_EmptyString_ShouldReturn_False;
begin
  CheckEquals(FInterfaceUnderTest.SomeMethod(''), False);
end;

I am sure you could do some clever magic using extended RTTI and put attributes on this class to register specific test classes for your classes that implement ISomeInterface. But in that case you are probably better off switching from DUnit to DUnitX. Besides which it is hardly any more trouble to register your implementing classes to be tested without using attributes. All you need do is:

  RegisterTest(TSomeInterfaceContractTests<TMyImplementerOfSomeInterface>.Suite);
  RegisterTest(TSomeInterfaceContractTests<TMyOtherImplementerOfSomeInterface>.Suite);

DUnit straight up

If you are using a Delphi version which does not support generics, then you need to do a bit more work yourself, but the basics are the same.

You create a “contract” test case class that contains the test methods to ensure an implementer of ISomeInterface meets the expected behavior.

type
  TSomeInterfaceContractTests = class(TTestCase)
  strict private
    FInterfaceUnderTest: ISomeInterface;
  published
    procedure SomeMethod_Input_EmptyString_ShouldReturn_False;
  end;

TSomeInterfaceContractTests.SomeMethod_Input_EmptyString_ShouldReturn_False;
begin
  CheckEquals(FInterfaceUnderTest.SomeMethod(''), False);
end;

To instantiate the proper class for your test methods you have several options. The one I like best uses the beauty of Delphi’s meta classes. For this you add a virtual abstract method to your contract test case.

type
  TSomeInterfaceContractTests = class(TTestCase)
  strict protected
    function GetImplementingClass: TInterfacedClass; virtual; abstract;

And use this in your SetUp override or in some other method you use to instantiate the class under test:

TSomeInterfaceContractTests.SetUp;
begin
  inherited;
  Supports(GetImplementingClass.Create, ISomeInterface, FInterfaceUnderTest);
  Assert(Assigned(FInterfaceUnderTest), 'Implementing class does not support ISomeInterface');
end;

Derived test classes must then of course override the GetImplementingClass method and return the class of the implementing class they are intended to exercise.

type
  TMyImplemterOfSomeInterfaceTest = class(TSomeInterfaceContractTests)
  strict protected
    function GetImplementingClass: TInterfacedClass; override;

TMyImplemterOfSomeInterfaceTest.GetImplementingClass: TInterfacedClass;
begin
  Result := TMyImplementerOfSomeInterface;
end;

Now all that is left to do is to register your derived test case(s) so that the test runner will execute all the test methods declared in the contract tests.

  RegisterTest(TMyImplemterOfSomeInterfaceTest.Suite);

And when you have another class implementing the ISomeInterface interface. All you need to do to have it tested for its implementation of that interface is:

  1. derive a test case class for that specific class from TSomeInterfaceContractTest
  2. override the GetImplementingClass method
  3. register the test case with DUnit.


3 comments on “How do I test an interface? Should I even do that?
  1. A. Bouchez says:

    Even without generics, you have full interface mocking and stubbing with mORMot.
    See http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_62

    It has unique features and full integration with our test classes.

    • Marjan Venema says:

      Sounds nice. Tell me though, why would I want to get a full blown ORM (Object Relational Mapper) if I only want stubbing and mocking? I don’t much like getting a house when I just need a bathroom? Now, if the stubbing and mocking features were available separately…

    • A. Bouchez says:

      No, of course the ORM has nothing to do with those stubs and mocks.
      Mocking is implemented in the same main unit, mORMot.pas, but both features are not linked to each other. Only some low-level optimized “fake class instance” creation, and low-level JSON process (used for the execution tracing, which is quite unique among other Delphi stubbing frameworks AFAIK), are shared.
      Thanks to Delphi compiler smart linking, you can use stubbing/mocking features without having any link to the ORM code of the framework in your executable.
      This feature works from Delphi 6 up to latest XE7, by the way – so you can also use it with legacy apps.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Show Buttons
Hide Buttons