I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll analyze various use cases and possible alternative solutions to unit-testing of abstract classes with non-abstract methods.

Note that testing abstract classes should almost always go through the public API of the concrete implementations, so don’t apply the below techniques unless you’re sure what you’re doing.

2. Maven Dependencies

Let’s start with Maven dependencies:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.1.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.8.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.7.4</version>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>1.7.4</version>
    <scope>test</scope>
</dependency>

You can find the latest versions of these libraries on Maven Central.

Powermock isn’t fully supported for Junit5. Also, powermock-module-junit4 is used only for one example presented in section 5.

3. Independent Non-Abstract Method

Let’s consider a case when we have an abstract class with a public non-abstract method:

public abstract class AbstractIndependent {
    public abstract int abstractFunc();

    public String defaultImpl() {
        return "DEFAULT-1";
    }
}

We want to test the method defaultImpl(), and we have two possible solutions – using a concrete class, or using Mockito.

3.1. Using a Concrete Class

Create a concrete class which extends AbstractIndependent class, and use it to test the method:

public class ConcreteImpl extends AbstractIndependent {

    @Override
    public int abstractFunc() {
        return 4;
    }
}
@Test
public void givenNonAbstractMethod_whenConcreteImpl_testCorrectBehaviour() {
    ConcreteImpl conClass = new ConcreteImpl();
    String actual = conClass.defaultImpl();

    assertEquals("DEFAULT-1", actual);
}

The drawback of this solution is the need to create the concrete class with dummy implementations of all abstract methods.

3.2. Using Mockito

Alternatively, we can use Mockito to create a mock:

@Test
public void givenNonAbstractMethod_whenMockitoMock_testCorrectBehaviour() {
    AbstractIndependent absCls = Mockito.mock(
      AbstractIndependent.class, 
      Mockito.CALLS_REAL_METHODS);
 
    assertEquals("DEFAULT-1", absCls.defaultImpl());
}

The most important part here is the preparation of the mock to use the real code when a method is invoked using Mockito.CALLS_REAL_METHODS.

4. Abstract Method Called From Non-Abstract Method

In this case, the non-abstract method defines the global execution flow, while the abstract method can be written in different ways depending upon the use case:

public abstract class AbstractMethodCalling {

    public abstract String abstractFunc();

    public String defaultImpl() {
        String res = abstractFunc();
        return (res == null) ? "Default" : (res + " Default");
    }
}

To test this code, we can use the same two approaches as before – either create a concrete class or use Mockito to create a mock:

@Test
public void givenDefaultImpl_whenMockAbstractFunc_thenExpectedBehaviour() {
    AbstractMethodCalling cls = Mockito.mock(AbstractMethodCalling.class);
    Mockito.when(cls.abstractFunc())
      .thenReturn("Abstract");
    Mockito.doCallRealMethod()
      .when(cls)
      .defaultImpl();

    assertEquals("Abstract Default", cls.defaultImpl());
}

Here, the abstractFunc() is stubbed with the return value we prefer for the test. This means that when we call the non-abstract method defaultImpl(), it will use this stub.

5. Non-Abstract Method with Test Obstruction

In some scenarios, the method we want to test calls a private method which contains a test obstruction.

We need to bypass the obstructing test method before testing the target method:

public abstract class AbstractPrivateMethods {

    public abstract int abstractFunc();

    public String defaultImpl() {
        return getCurrentDateTime() + "DEFAULT-1";
    }

    private String getCurrentDateTime() {
        return LocalDateTime.now().toString();
    }
}

In this example, the defaultImpl() method calls the private method getCurrentDateTime(). This private method gets the current time at runtime, which should be avoided in our unit tests.

Now, to mock the standard behavior of this private method, we cannot even use Mockito because it cannot control private methods.

Instead, we need to use PowerMock (note that this example works only with JUnit 4 because support for this dependency isn’t available for JUnit 5):

@RunWith(PowerMockRunner.class)
@PrepareForTest(AbstractPrivateMethods.class)
public class AbstractPrivateMethodsUnitTest {

    @Test
    public void whenMockPrivateMethod_thenVerifyBehaviour() {
        AbstractPrivateMethods mockClass = PowerMockito.mock(AbstractPrivateMethods.class);
        PowerMockito.doCallRealMethod()
          .when(mockClass)
          .defaultImpl();
        String dateTime = LocalDateTime.now().toString();
        PowerMockito.doReturn(dateTime).when(mockClass, "getCurrentDateTime");
        String actual = mockClass.defaultImpl();

        assertEquals(dateTime + "DEFAULT-1", actual);
    }
}

Important bits in this example:

  • @RunWith defines PowerMock as the runner for the test
  • @PrepareForTest(class) tells PowerMock to prepare the class for later processing

Interestingly, we’re asking PowerMock to stub the private method getCurrentDateTime(). PowerMock will use reflection to find it because it’s not accessible from outside.

Sowhen we call defaultImpl(), the stub created for a private method will be invoked instead of the actual method.

6. Non-Abstract Method Which Accesses Instance Fields

Abstract classes can have an internal state implemented with class fields. The value of the fields could have a significant effect on the method getting tested.

If a field is public or protected, we can easily access it from the test method.

But if it’s private, we have to use PowerMockito:

public abstract class AbstractInstanceFields {
    protected int count;
    private boolean active = false;

    public abstract int abstractFunc();

    public String testFunc() {
        if (count > 5) {
            return "Overflow";
        } 
        return active ? "Added" : "Blocked";
    }
}

Here, the testFunc() method is using instance-level fields count and active before it returns.

When testing testFunc(), we can change the value of the count field by accessing instance created using Mockito. 

On the other hand, to test the behavior with the private active field, we’ll again have to use PowerMockito, and its Whitebox class:

@Test
public void whenPowerMockitoAndActiveFieldTrue_thenCorrectBehaviour() {
    AbstractInstanceFields instClass = PowerMockito.mock(AbstractInstanceFields.class);
    PowerMockito.doCallRealMethod()
      .when(instClass)
      .testFunc();
    Whitebox.setInternalState(instClass, "active", true);

    assertEquals("Added", instClass.testFunc());
}

We’re creating a stub class using PowerMockito.mock(), and we’re using Whitebox class to control object’s internal state.

The value of the active field is changed to true.

7. Conclusion

In this tutorial, we’ve seen multiple examples which cover a lot of use cases. We can use abstract classes in many more scenarios depending upon the design followed.

Also, writing unit tests for abstract class methods is as important as for normal classes and methods. We can test each of them using different techniques or different test support libraries available.

The full source code is available over on GitHub.

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE LESSONS