89

What is the best way to write junit tests for interfaces so they can be used for the concrete implementing classes?

e.g. You have this interface and implementing classes:

public interface MyInterface {
    /** Return the given value. */
    public boolean myMethod(boolean retVal);
}

public class MyClass1 implements MyInterface {
    public boolean myMethod(boolean retVal) {
        return retVal;
    }
}

public class MyClass2 implements MyInterface {
    public boolean myMethod(boolean retVal) {
        return retVal;
    }
}

How would you write a test against the interface so you can use it for the class?

Possibility 1:

public abstract class MyInterfaceTest {
    public abstract MyInterface createInstance();

    @Test
    public final void testMyMethod_True() {
        MyInterface instance = createInstance();
        assertTrue(instance.myMethod(true));
    }

    @Test
    public final void testMyMethod_False() {
        MyInterface instance = createInstance();
        assertFalse(instance.myMethod(false));
    }
}

public class MyClass1Test extends MyInterfaceTest {
    public MyInterface createInstance() {
        return new MyClass1();
    }
}

public class MyClass2Test extends MyInterfaceTest {
    public MyInterface createInstance() {
        return new MyClass2();
    }
}

Pro:

  • Need only one method to be implemented

Con:

  • Dependencies and mock objects of class under test have to be the same for all tests

Possibility 2:

public abstract class MyInterfaceTest
    public void testMyMethod_True(MyInterface instance) {
        assertTrue(instance.myMethod(true));
    }

    public void testMyMethod_False(MyInterface instance) {
        assertFalse(instance.myMethod(false));
    }
}

public class MyClass1Test extends MyInterfaceTest {
    @Test
    public void testMyMethod_True() {
        MyClass1 instance = new MyClass1();
        super.testMyMethod_True(instance);
    }

    @Test
    public void testMyMethod_False() {
        MyClass1 instance = new MyClass1();
        super.testMyMethod_False(instance);
    }
}

public class MyClass2Test extends MyInterfaceTest {
    @Test
    public void testMyMethod_True() {
        MyClass1 instance = new MyClass2();
        super.testMyMethod_True(instance);
    }

    @Test
    public void testMyMethod_False() {
        MyClass1 instance = new MyClass2();
        super.testMyMethod_False(instance);
    }
}

Pro:

  • fine granualtion for each test including dependencies and mock objects

Con:

  • Each implementing test class requires to write additional test methods

Which possibility would you prefer or what other way do you use?

Xeno Lupus
  • 1,504
  • 3
  • 12
  • 17
  • Possibility 1 is not sufficient when the concrete class is in a different package, component or development team. – Andy Thomas Jul 17 '11 at 14:20
  • 1
    @AndyThomas: Why do you say that? I use possibility 1 with concrete classes (both for the implementations and tests) in different packages and Maven projects. – Trevor Robinson Apr 15 '14 at 22:43
  • 1
    @TrevorRobinson - Thinking back about that three-year-old comment, all that I can think of at the moment is that classes outside your control may have multiple constructors, but possibility 1 runs each test on an object created only with one of those. – Andy Thomas Apr 16 '14 at 03:04
  • With option1, you can have separate @Before methods in each concrete class. You can also have one-off tests in concrete classes as you see fit. – JoshOrndorff Oct 02 '17 at 14:19
  • [Test annotations in interfaces are now possible in JUnit 5](https://stackify.com/junit-5/) and work as expected. – Steven Jeuris Jan 25 '18 at 14:50
  • @StevenJeuris this question isn't about adding annotations to "Test interfaces", but rather testing real interfaces – GMeister Jan 25 '18 at 15:47
  • @GMeister What are 'real' interfaces? In JUnit 5, you can create an interface and add tests to it. Subsequently, you can extend from the interface and the tests specified in the interface will be run on the concrete class. – Steven Jeuris Jan 26 '18 at 09:07
  • @StevenJeuris If I've understood you correctly, you're talking about JUnit Interfaces, i.e. Interfaces to be used by JUnit tests - at least that's what your link references. This question is instead about testing the Interfaces of functional code (as opposed to test code). – GMeister Jan 30 '18 at 09:57

6 Answers6

89

Contrary to the much-voted-up answer that @dlev gave, it can sometimes be very useful/needful to write a test like you're suggesting. The public API of a class, as expressed through its interface, is the most important thing to test. That being said, I would use neither of the approaches you mentioned, but a Parameterized test instead, where the parameters are the implementations to be tested:

@RunWith(Parameterized.class)
public class InterfaceTesting {
    public MyInterface myInterface;

    public InterfaceTesting(MyInterface myInterface) {
        this.myInterface = myInterface;
    }

    @Test
    public final void testMyMethod_True() {
        assertTrue(myInterface.myMethod(true));
    }

    @Test
    public final void testMyMethod_False() {
        assertFalse(myInterface.myMethod(false));
    }

    @Parameterized.Parameters
    public static Collection<Object[]> instancesToTest() {
        return Arrays.asList(
                    new Object[]{new MyClass1()},
                    new Object[]{new MyClass2()}
        );
    }
}
Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
  • 4
    There seems to be a problem with this approach. The same instance of MyClass1 and MyClass2 is used to execute all the test methods. Ideally, every testMethod should be executed with a new instance of MyClass1/MyClass2, this drawback renders this approach not usable. – ChrisOdney Apr 26 '13 at 12:27
  • 14
    If you need a new fixture instance for each test method, then have the parameters method return a factory that each test invokes to get its fixture. It doesn't affect the viability of this approach. – Ryan Stewart Apr 27 '13 at 14:40
  • 1
    what about putting class reference to parameters, and in "InterfaceTesting" method instantiate with reflection? – ArcanisCz Oct 13 '13 at 12:21
  • 2
    @ArcanisCz: As with the eariler commenter, *how* you get the instance to test isn't that important. The important point is that some kind of parameterized test is likely the right approach. – Ryan Stewart Oct 13 '13 at 14:09
  • 4
    Suppose I write an interface, provide a few implementations and a test written like this. If a user creates a new implementation and wants to test it, he has to modify the source code of the test. This makes me consider the abstract method returning the testing instance more useful, especially if you expect your clients to create their own implementations. – jspurim Aug 03 '15 at 01:03
  • I might be missing something, but it doesn't appear this is still possible as the instances passed into the Test now appear to be triggering a type mismatch in JUnit. Any modern alternatives? – GMeister Jan 25 '18 at 15:48
  • @GMeister did you find any alternative? – Aviral Verma Oct 27 '20 at 15:54
  • @AviralVerma Alas that project is now long-gone for me... sorry! – GMeister Nov 12 '20 at 16:29
22

I strongly disagree with @dlev. Very often it is a very good practice writing tests that use interfaces. Interface defines contract between client and the implementation. Very often all your implementations must pass exactly the same tests. Obviously each implementation can have its own tests.

So, I know 2 solutions.

  1. Implement abstract test case with various tests that use interface. Declare abstract protected method that returns concrete instance. Now inherit this abstract class as many times as you need for each implementation of your interface and implement the mentioned factory method accordingly. You can add more specific tests here as well.

  2. Use test suites.

AlexR
  • 114,158
  • 16
  • 130
  • 208
16

I disagree with dlev as well, there's nothing wrong with writing your tests against interfaces instead of concrete implementations.

You probably want to use parameterized tests. Here is what it would look like with TestNG, it's a little more contrived with JUnit (since you can't pass parameters directly to test functions):

@DataProvider
public Object[][] dp() {
  return new Object[][] {
    new Object[] { new MyImpl1() },
    new Object[] { new MyImpl2() },
  }
}

@Test(dataProvider = "dp")
public void f(MyInterface itf) {
  // will be called, with a different implementation each time
}
Cedric Beust
  • 15,480
  • 2
  • 55
  • 55
13

Late addition to the subject, sharing newer solution insights

I'm also looking for a proper and efficient way of testing (based on JUnit) correctness of multiple implementations of some interfaces and abstract classes. Unfortunately, neither JUnit's @Parameterized tests nor TestNG's equivalent concept correctly fits my requirements, since I don't know a priori the list of implementations of these interface/abstract classes that might exists. That is, new implementations might be developped, and testers might not have access to all existing implementations; it is therefore not efficient to have test classes specify the list of implementation classes.

At this point, I have found the following project which seems to offer a complete and efficient solution to simplify this type of tests: https://github.com/Claudenw/junit-contracts . It basically allows the definition of "Contract Tests", through the annotation @Contract(InterfaceClass.class) on contract test classes. Then an implementer would create an implementation specific test class, with annotations @RunWith(ContractSuite.class) and @ContractImpl(value = ImplementationClass.class); the engine shall automatically apply any contract test that applies to ImplementationClass, by looking for all Contract Test defined for any interface or abstract class from which ImplementationClass derives. I have not yet tested this solution, but this sounds promising.

I have also found the following library: http://www.jqno.nl/equalsverifier/ . This one satisfies a similar though much more specific need, which is asserting a class conformity specifically to Object.equals and Object.hashcode contracts.

Similarly, https://bitbucket.org/chas678/testhelpers/src demonstrate a strategy to validate some Java fondamental contracts, including Object.equals, Object.hashcode, Comparable.compare, Serializable. This project use simple test structures, which, I believe, can be easily reproduced to suite any specific needs.

Well, that's it for now; I'll keep this post updated with other usefull informations I may find.

James
  • 4,211
  • 1
  • 18
  • 34
6

I would generally avoid writing unit tests against an interface, for the simple reason that an interface, however much you would like it to, does not define functionality. It encumbers its implementors with syntactic requirements, but that's it.

Unit tests, conversely, are intended to ensure that the functionality you expect is present in a given code path.

That being said, there are situations where this type of test could make sense. Assuming you wanted these tests to ensure that classes you wrote (that share a given interface) do, in fact, share the same functionality, then I would prefer your first option. It makes it easiest on the implementing subclasses to inject themselves into the testing process. Also, I don't think your "con" is really true. There's no reason you can't have the classes actually under test provide their own mocks (though I think that if you really need different mocks, then that suggests your interface tests aren't uniform anyway.)

dlev
  • 48,024
  • 5
  • 125
  • 132
  • 30
    An interface doesn't define functionality, but it defines the API which its implementations must abide by. That's what a test should focus on, so why wouldn't one write a test to express that? Particularly when you are making good use of polymorphism with multiple implementations of an interface, this kind of test is extremely valuable. – Ryan Stewart Jul 17 '11 at 14:49
  • 2
    I agree with dlev - I don't see a point in testing an interface. The compiler will tell you if your concrete implementation fails to implement the interface. I see no value in it whatsoever. Unit tests are for concrete classes. – duffymo Jul 17 '11 at 14:54
  • @Ryan (and everyone else who disagrees with me (some strongly)): I understand that it is convention that an interface defines a contract/API/etc. And in fact, as the last part of my answer indicates, under certain conditions, I do think it's a good idea (though I'll update to make that clearer.) However, it's still just convention. If I implement an interface, I don't have to follow the API. In C# I'm free to throw NotImplementedExceptions for some members if I want. – dlev Jul 17 '11 at 14:55
  • @duffymo From a colleague the other day: "Q: How do you unit test an interface? A: Rebuild!." Not fair, perhaps, but worth a chuckle anyway. – dlev Jul 17 '11 at 14:57
  • 6
    @Ryan - an interface contract is rarely "just a convention." A contract communicates firm expectations of recipients of the interface. Violations of the contract may be allowed by the compiler, but will frequently cause unexpected behavior at runtime. A single definition of this contract in unit tests is better than multiple. – Andy Thomas Jul 18 '11 at 21:20
  • @Andy: I didn't say that. I agree with you wholeheartedly. You want @dlev. My answer (a parameterized test) seems to be a good way of testing multiple implementations of an interface. – Ryan Stewart Jul 18 '11 at 21:33
  • 1
    For people disagreeing on writing unit tests for an interface - if there are multiple implementations for LinkedList like Singly, Doubly, Circular etc. Why should I rewrite the unit tests that are common, isn't it more convenient to have a parameterized test? @duffymo are you the same duffymo from sun forums? – ChrisOdney Apr 26 '13 at 12:35
  • 5
    I would test through the interface only functionality that is part of contract, ex: when item is **add**ed to the list for the first time, **size** should become 1, no matter which implementation is being used, this invariant should hold. However additional tests might be needed for the implementations, which test functionality specific to only that implementation. – tsobe Mar 11 '16 at 16:31
  • For me, the value would be writing the same test to be used against multiple implementations. Specifically, one can write a naive implementation that should pass the tests and can use for development until a more robust implementation can be created for production use. But they both should pass the same test. – corsiKa Nov 21 '17 at 15:43
2

with java 8 i do this

public interface MyInterfaceTest {
   public MyInterface createInstance();

   @Test
   default void testMyMethod_True() {
       MyInterface instance = createInstance();
       assertTrue(instance.myMethod(true));
   }

   @Test
   default void testMyMethod_False() {
       MyInterface instance = createInstance();
       assertFalse(instance.myMethod(false));
   }
}

public class MyClass1Test implements MyInterfaceTest {
    public MyInterface createInstance() {
        return new MyClass1();
    }
}

public class MyClass2Test implements MyInterfaceTest {
   public MyInterface createInstance() {
       return new MyClass2();
   }

   @Disabled
   @Override
   @Test
   public void testMyMethod_True() {
       MyInterfaceTest.super.testMyMethod_True();
   };
}
Geert-Jan Hut
  • 140
  • 2
  • 5
  • the "abstract" keyword is needless. – Ubuntix Oct 09 '18 at 14:08
  • This approach is documented more extensively in the JUnit 5 User Guide, in the section [Test Interfaces and Default Methods](https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-interfaces-and-default-methods). – Geert-Jan Hut Aug 31 '19 at 13:53