1. Overview

Mocking protected method in Java is similar to mocking a public one, with one caveat: visibility of this method in the test class. We have visibility of protected methods of class A from the same package classes and ones that extend A. So, if we try to test class A from a different package, we’ll face issues.

In this tutorial, we’ll approach the case of mocking protected method of the class under test. We’ll demonstrate both cases of having access to the method and not. We’ll do that by using Mockito spies instead of mocks since we want to stub just some behavior of the under-test class.

2. Mocking protected Method

Mocking protected method with Mockito is straightforward when we have access to it. We can get access in two ways. First, we change the scope of protected to public, or second, we move the test class into the same package as the class with the protected method.

But this isn’t an option sometimes, and so an alternative is to follow indirect practices. The most common ones, without using any external libraries, are:

  • to use JUnit5 and Reflection
  • to use an inner test class that extends the class under test

If we try to change the access modifier, this might lead to unwanted behavior. Using the most restrictive access level is good practice unless there is a good reason not to. Similarly, if it makes sense to change the location of the test, then moving it to the same package as the class with the protected method is an easy option.

If neither of these options works for our case, JUnit5 with Reflection is a good call, when there is only one protected method to stub in the class. For a class A with more than one protected method that we need to stub, creating an inner class that extends A is the cleaner solution.

3. Mocking Visible protected Method

In this section, we’ll handle the cases in which the test has access to the protected method, or we can make changes to get access. As mentioned before, the changes could be making the access modifier public or moving the test to the same package as the class with the protected method.

Let’s see the Movies class as an example, which has a protected method getTitle() to retrieve the value of private field title. It also contains a public method getPlaceHolder(), which is available to clients:

public class Movies {
    private final String title;

    public Movies(String title) {
        this.title = title;
    }

    public String getPlaceHolder() {
        return "Movie: " + getTitle();
    }

    protected String getTitle() {
        return title;
    }
}

In the test class, first, we assert that the initial value of the getPlaceholder() method is the one we expect. Then we stub the functionality of the protected method using Mockito spies and we assert that the new value getPlaceholder() returns contains the stubbed value of getTitle():

@Test
void givenProtectedMethod_whenMethodIsVisibleAndUseMockitoToStub_thenResponseIsStubbed() {
    Movies matrix = Mockito.spy(new Movies("The Matrix"));
    assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: The Matrix");

    doReturn("something else").when(matrix).getTitle();

    assertThat(matrix.getTitle()).isEqualTo("something else");
    assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: something else");
}

4. Mocking Non-Visible protected Method

Next, let’s see how mocking a protected method with Mockito is done when we don’t have access to it. The use case we’ll deal with is when the test class is in a different package than the class we want to stub. In such case, we have two options:

  • JUnit5 with Reflection
  • inner class which extends the class with the protected method

4.1. Using JUnit and Reflection

JUnit5 provides a class, ReflectionSupport, that handles common reflection cases for testing, like finding/invoking methods, etc. Let’s see how this works with our previous code:

@Test
void givenProtectedMethod_whenMethodIsVisibleAndUseMockitoToStub_thenResponseIsStubbed() throws NoSuchMethodException {
    Movies matrix = Mockito.spy(new Movies("The Matrix"));
    assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: The Matrix");

    ReflectionSupport.invokeMethod(
            Movies.class.getDeclaredMethod("getTitle"),
            doReturn("something else").when(matrix));

    assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: something else");
}

Here, we use the invokeMethod() of ReflectionSupport that sets the value of the protected method to be the stubbed Movie object, when invoked.

4.2. Using Inner Class

We can overcome the visibility issue by creating an inner class that extends the class under test and makes the protected method visible. The inner class can be a class on its own if we need to mock the same class’s protected method in different test classes.

In our case, it makes sense to have the MoviesWrapper class that extends Movies, from our previous code, as an inner class of the test class:

private static class MoviesWrapper extends Movies {
    public MoviesWrapper(String title) {
        super(title);
    }

    @Override
    protected String getTitle() {
        return super.getTitle();
    }
}

This way, we get access to getTitle() of Movies through the MoviesWrapper class. If, instead of an inner class we use a standalone one, the method access modifier might need to become public.

The test then uses the MoviesWrapper class as the class under test. This way we have access to getTitle() and can easily stub it using Mockito spies:

@Test
void givenProtectedMethod_whenMethodNotVisibleAndUseInnerTestClass_thenResponseIsStubbed() {
    MoviesWrapper matrix = Mockito.spy(new MoviesWrapper("The Matrix"));
    assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: The Matrix");

    doReturn("something else").when(matrix).getTitle();

    assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: something else");
}

5. Conclusion

In this article, we discussed the difficulties with visibility when mocking protected methods in Java and demonstrated the possible solutions. There are different options for each use case we might face and based on the examples, we should be able to pick the right one each time.

As always, all the source code is available over on GitHub.