Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit under test: Impl or Interface?

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.

like image 403
alexsmail Avatar asked Jun 07 '12 18:06

alexsmail


People also ask

Should you test the interface or the implementation?

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.

Can we write unit test for interface?

You can't. It has no implementation. 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.

Can we test interface in JUnit?

Test Interfaces and Default Methods On interface default methods, JUnit Jupiter allows you to specify @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate, @BeforeEach, and @AfterEach. If the test interface or test class is annotated with @TestInstance(Lifecycle.

What is implementation unit testing?

As defined on MSDN, the primary goal of unit testing is to take the smallest piece of testable software in the application, isolate it from the remainder of the code, and determine whether it behaves exactly as you expect.


2 Answers

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.

like image 116
Tim Bender Avatar answered Oct 11 '22 18:10

Tim Bender


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);
    }
}
like image 9
duffymo Avatar answered Oct 11 '22 16:10

duffymo