Unit testing interface implementations

It's bugged me for a while about how best to go about unit testing classes that implement multiple interfaces where those interfaces are implemented by multiple classes or where a class extends another class and implements one or more interfaces.

What I want to avoid is in each test class implementing the the same set of tests for the behavior specified by the interface. That's not very DRY.

If your classes only implement a single interface then you can have a base class with the tests for the interface and then separate test classes derived from that for each class implementing the interface. The problem arises when your classes implement multiple interfaces - Java doesn't support multiple inheritance for classes so you can't have a base test case for each interface.

One answer with JUnit 4 is to use the parameterized test runner - I haven't thought about JUnit 3, if you're not using JUnit 4 because you're still using a JVM that doesn't support annotations JSE 6 is out now, catch up!

You need to have a test case for your interface and configure this to run with the parameterized runner. You also have a static factory method that provides the various implementations of the interface to be used in each test.

As an example, say the container I'm writing (I'm not really - I know there is Apache Geronimo, JBoss, Hivemind etc - this is just an example) supports services that it can start and stop, so I have an interface Service and the various services that I have, e.g. caching, lookup, logging etc, implement this interface.

JAVA:
  1. /**
  2. * Represents a service that the container can run.
  3. *
  4. */
  5. public interface Service {
  6.  
  7.     /**
  8.       * Starts the service, may only be called if the service is not started.
  9.       *
  10.       * @throws IllegalStateException
  11.       *             if called when the service is already started.
  12.       */
  13.     public void start();
  14.  
  15.     /**
  16.       * Indicates whether the service is started
  17.       *
  18.       * @return true if the service is started, false otherwise
  19.       */
  20.     public boolean isStarted();
  21. }

I have a test case for Service that tests that the various implementations of Service adhere to the general contract. I might have the following three tests:

JAVA:
  1. public class ServiceTest {
  2.  
  3.     private Service service;
  4.  
  5.     @Test
  6.     public void shouldNotIndicatedStartedForNewObject() {
  7.         assertFalse(service.isStarted());
  8.     }
  9.  
  10.     @Test
  11.     public void shouldStartIfNotAlreadyStarted() {
  12.         service.start();
  13.         assertTrue(service.isStarted());
  14.     }
  15.  
  16.     @Test(expected = IllegalStateException.class)
  17.     public void shouldThrowExceptionIfStartedCalledOnAStartedService() {
  18.         service.start();
  19.         assertTrue(service.isStarted());
  20.         service.start();
  21.     }
  22. }

If I have a caching and lookup service and I want to test that both adhere to the contract of the Service interface then I have to annotate the ServiceTest to indicate that it should run with the parameterized runner and provide an annotated factory method that returns a collection of arguments to run the tests with.

So the full testcase is:

JAVA:
  1. @RunWith(Parameterized.class)
  2. public class ServiceTest {
  3.  
  4.     private Service service;
  5.  
  6.     public ServiceTest(final Service service) {
  7.         this.service = service;
  8.     }
  9.  
  10.     @Parameters
  11.     public static Collection implementations() {
  12.         return Arrays.asList(new Object[][] { { new CachingService() }, { new LookupService() } });
  13.     }
  14.  
  15.     @Test
  16.     public void shouldNotIndicatedStartedForNewObject() {
  17.         assertThat(service, is(not(started())));
  18.     }
  19.  
  20.     @Test
  21.     public void shouldStartIfNotAlreadyStarted() {
  22.         service.start();
  23.         assertThat(service, is(started()));
  24.     }
  25.  
  26.     @Test(expected = IllegalStateException.class)
  27.     public void shouldThrowExceptionIfStartedCalledOnAStartedService() {
  28.         service.start();
  29.         assertThat(service, is(started()));
  30.         service.start();
  31.     }
  32. }

The test classes for CachingService and LookupService now only need deal with testing the specifics of caching or lookups rather than testing adherence to the service specification.

I have yet to use this in production code on a large scale so we'll have to see how it works out, but it seems far better than having to replicate test code amongst several test cases.

7 Responses to “Unit testing interface implementations”

  1. Screened Twenty says:

    Hello

    We’ve got a non-profit site here with an article on Unit Testing in general, located at: http://coderslog.com/Unit_Testing_101

    Hoping to get examples on there soon. The site itself is a journal of common code/configurations and other hard to find information. Should come in handy for building high performance large scale java applications and such.

  2. Ilya Bobir says:

    Don’t you think that enumerating all the interface implementations in the interface test is a bit wrong?
    It seems to me it would be more correct to have some kind of a generic test suite that tests the interface contract and use it as a part of a test suite for each implementation.

  3. Paul says:

    I can’t comment on how well this works in practice as since I wrote the post I’ve been working solely with Ruby.

    In the past I’ve alway tried to avoid suites as test cases inevitable accidentally get left out however in this case either way you’d have to remember to add an entry to something, either the suite or the interface testcase.

    As I’ve not used suites much I’m not sure how go about telling the generic test which implementation of the interface to test, or I may have misunderstood what you meant.

    One other small benefit that has just come to mind of having all the implementations of an interface listed in the test for the interface is that if you change the interface you just need to run the test for it to discover which classes are broken rather than having to remove all compiled class files and compiling everything to pick up which classes were dependent on the interface.

  4. Ilya Bobir says:

    Suites can be connected to form a tree. A suite for the interface contract can be added as a child to the suite that tests the class.

    About telling which implementation of the interface to test, it is quite easy with JUint 3 where you are creating suites yourself. You can just add a method to the suite that will take an implementation factory and will propagate it into all contained tests. Then you need to do a call to supply the factory in addition to attaching the interface test suite as a child to your class test suite.

  5. Ilya Bobir says:

    For some reason I can not submit the rest of my post :(

  6. Ilya Bobir says:

    It constantly gives me an error. Please check the server logs. =)

  7. [...] functionality of abstract classes works properly in all concrete subclasses. Certainly this has been mentioned before, generally under the name Abstract Tests or Contract Tests. Also, the idea seems [...]

Leave a Reply