31
votes

Suppose I have interface and implementation class that implements it and I want to write unit-test for this. What should I test interface or Impl?

Here is an example:

public interface HelloInterface {
    public void sayHello();
}


public class HelloInterfaceImpl implements HelloInterface {
    private PrintStream target = System.out;


    @Override
    public void sayHello() {
        target.print("Hello World");

    }

    public void setTarget(PrintStream target){
        this.target = target;
    }
}

So, I have HelloInterface and HelloInterfaceImpl that implements it. What is unit-under-test interface or Impl?

I think it should be HelloInterface. Consider following sketch of JUnit test:

public class HelloInterfaceTest {
    private HelloInterface hi;

    @Before
    public void setUp() {
        hi = new HelloInterfaceImpl();
    }

    @Test
    public void testDefaultBehaviourEndsNormally() {
        hi.sayHello();
        // no NullPointerException here
    }

    @Test
    public void testCheckHelloWorld() throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream target = new PrintStream(out);
        PrivilegedAccessor.setValue(hi, "target", target);
        //You can use ReflectionTestUtils in place of PrivilegedAccessor
        //really it is DI 
        //((HelloInterfaceImpl)hi).setTarget(target);
        hi.sayHello();
        String result = out.toString();
        assertEquals("Hello World", result);

    }
 }

The main line is actually one that I commented out.

((HelloInterfaceImpl)hi).setTarget(target);

Method setTarget() is not part of my public interface, so I don't want to accidentally call it. If I really want to call it, I should take a moment and think about it. It helps me, for example, to discover that what I'm really trying to do is dependency injection. It opens for me the whole world of new opportunities. I can use some existing dependency injection mechanism (Spring's, for example), I can simulate it myself as I actually did in my code or to take totally different approach. Take a closer look, preparation of PrintSream wasn't that easy, maybe I should use mock object instead?

EDIT: I think I should always focus on the interface. From my point of view setTarget() is not part of the "contract" of the impl class neither, it serves sally for dependency injection. I think any public method of Impl class should be considered as private from the testing perspective. It doesn't mean that I ignore the implementation details, though.

See also Should Private/Protected methods be under unit test?

EDIT-2 In the case of multiple implementations\multiple interfaces, I would test all of the implementations, but when I declare a variable in my setUp() method I would definitely use interface.

4

4 Answers

19
votes

The implementation is the unit that needs to be tested. That is of course what you are instantiating and what contains the program/business logic.

If you had a critical interface and you wanted to make sure every implementation adhered to it properly, then you may write a test suite that focuses on the interface and requires an instance be passed in (agnostic of any implementation type).

Yes, it would probably be easier to use Mockito for PrintStream, it may not always be possible to avoid using a mock object like you did in this specific example.

8
votes

I would test to the interface.

I think the mistake was writing the implemenation in such a way that it was hard-wired to write to System.out; you gave yourself no way to override with another PrintStream. I would have used a constructor instead of a setter. There's no need for a mock or casting that way.

This is a simple case. I'd imagine that a more complex one would have a factory for creating different, more complex implementations of an interface. Hopefully you wouldn't design that in such a way that you'd be boxed in.

Sticking to the interface in your tests makes mocking a lot easier, too.

public class HelloInterfaceImpl implements HelloInterface {

    private PrintStream target;

    public HelloInterfaceImpl() {
        this(System.out);
    }

    public HelloInterfaceImpl(PrintStream ps) { 
       this.target = ps;
    }

    @Override
    public void sayHello() {
        target.print("Hello World");
    }
}

Here's the test:

public class HelloInterfaceTest {

    @Test
    public void testDefaultBehaviourEndsNormally() {
        HelloInterface hi = new HelloInterfaceImpl();    
        hi.sayHello();
    }

    @Test
    public void testCheckHelloWorld() throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream target = new PrintStream(out);
        HelloInterface hi = new HelloInterfaceImpl(target);    
        hi.sayHello();
        String result = out.toString();
        assertEquals("Hello World", result);
    }
}
7
votes

I always test implementations - one class can implement several interfaces and also one interface can be implemented by several classes - each of them should be covered by tests.

The requirement of invoking setter in the unit test (casting the interface to the implementation):

((HelloInterfaceImpl)hi).setTarget(target);

means that you actually test the implementation. It's not the part of the contract, but this is the important part making the implementation to work and should be tested properly.

Let's take an example from JDK. You have interface List and two implementations: ArrayList and LinkedList. Additionally LinkedList implements Deque interface. If you write test for List interface what would you cover? Array or linked list? What's more in case of LinkedList, what interface would you choose to test? Deque or List? As you see, when you test implementations you don't have such problems.

For me, personally, casting interface to implementation in the unit test is obvious sign that something is going wrong ;)

1
votes

I would say it depends on the implementation and what it does beyond the contract of the interface. Many implementations only implement the functionality provided in the interface, in others, the interface is only a small portion of the classes functionality. It may implement multiple interfaces.

Ultimately you are testing the implementation.

In a simple case like you have defined, I say six of one and half a dozen of the other. Write your test cases to the interface or the implementation, as long as it tests the implementation sufficiently, the results are the same.

Take a different example where I would have a class that collects statistics on a communication channel by decorating the real reader and writer. My class may now implement those interfaces, but it also collects statistics, which has nothing to do with either contract. I could certainly still write tests based on those interfaces, but it will not fully test this class.