inner classes
private methods
class
test

How do I test a class that has private methods, fields or inner classes?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Testing a class with private methods, fields, or inner classes in Java (or similar languages) can be tricky since private members are not accessible directly outside the class. However, there are various techniques and strategies to test such private components indirectly or directly when necessary.


General Strategy: Test Public Methods

The most common and recommended approach is to test private methods indirectly through public methods.

  • Why? Private methods are implementation details, and testing them directly can make your tests tightly coupled to the class's internal implementation.
  • Public methods should exercise private methods, so testing public methods effectively validates the behavior of private methods.

Example:

java
1public class MyClass {
2    private int doubleValue(int x) {
3        return x * 2;
4    }
5
6    public int calculate(int input) {
7        return doubleValue(input) + 10;
8    }
9}

Test Code:

java
1import org.junit.jupiter.api.Test;
2import static org.junit.jupiter.api.Assertions.*;
3
4public class MyClassTest {
5    @Test
6    public void testCalculate() {
7        MyClass myClass = new MyClass();
8        int result = myClass.calculate(5);
9        assertEquals(20, result); // Tests behavior of 'doubleValue' indirectly
10    }
11}

Key Idea: The private method doubleValue() is tested indirectly because the public method calculate() relies on it.


Reflection allows you to access private methods or fields directly at runtime. This is useful in edge cases, but it should be avoided unless absolutely necessary because it breaks encapsulation.

Example: Testing a Private Method Using Reflection

java
1import org.junit.jupiter.api.Test;
2import java.lang.reflect.Method;
3
4import static org.junit.jupiter.api.Assertions.*;
5
6public class MyClassTest {
7    @Test
8    public void testPrivateMethod() throws Exception {
9        MyClass myClass = new MyClass();
10
11        // Access private method using reflection
12        Method privateMethod = MyClass.class.getDeclaredMethod("doubleValue", int.class);
13        privateMethod.setAccessible(true);
14
15        // Invoke the private method
16        int result = (int) privateMethod.invoke(myClass, 5);
17
18        // Assert the result
19        assertEquals(10, result);
20    }
21}

Explanation:

  1. getDeclaredMethod() retrieves the private method.
  2. setAccessible(true) allows access to the private method.
  3. invoke() calls the method with the provided arguments.

2. Use a Helper Class or Package-Private Access

If private methods are complex and need direct testing, you can refactor them to:

  • Package-private access: Remove private and make the method package-private (default access modifier).
  • Helper Class: Extract private methods into a new helper class that can be tested directly.

Refactoring Example:

java
1class Helper {
2    int doubleValue(int x) {
3        return x * 2;
4    }
5}
6
7public class MyClass {
8    private Helper helper = new Helper();
9
10    public int calculate(int input) {
11        return helper.doubleValue(input) + 10;
12    }
13}

Test Code:

java
1import org.junit.jupiter.api.Test;
2import static org.junit.jupiter.api.Assertions.*;
3
4public class HelperTest {
5    @Test
6    public void testDoubleValue() {
7        Helper helper = new Helper();
8        assertEquals(10, helper.doubleValue(5));
9    }
10}

3. Use Mocking to Test Private Dependencies

When private fields or methods depend on other classes, you can use mocking libraries like Mockito or PowerMockito to test behavior.

Example: Mocking a Private Field

If a private field is a dependency, you can use reflection or frameworks to set it for testing.

java
1import org.junit.jupiter.api.Test;
2import static org.mockito.Mockito.*;
3
4public class MyClassTest {
5    @Test
6    public void testWithMock() throws Exception {
7        MyClass myClass = new MyClass();
8
9        // Use reflection to set the private field
10        java.lang.reflect.Field helperField = MyClass.class.getDeclaredField("helper");
11        helperField.setAccessible(true);
12        Helper mockHelper = mock(Helper.class);
13
14        when(mockHelper.doubleValue(5)).thenReturn(20);
15        helperField.set(myClass, mockHelper);
16
17        // Test the calculate method
18        int result = myClass.calculate(5);
19        assertEquals(30, result);
20    }
21}

4. Test Inner Classes

For testing private inner classes, you can use reflection to access them or change their access to package-private.

Example:

java
1public class Outer {
2    private class Inner {
3        int add(int a, int b) {
4            return a + b;
5        }
6    }
7
8    public int useInner(int a, int b) {
9        Inner inner = new Inner();
10        return inner.add(a, b);
11    }
12}

Test Code (Indirect Test):

java
1import org.junit.jupiter.api.Test;
2import static org.junit.jupiter.api.Assertions.*;
3
4public class OuterTest {
5    @Test
6    public void testUseInner() {
7        Outer outer = new Outer();
8        assertEquals(5, outer.useInner(2, 3));
9    }
10}

If you need to test Inner directly, reflection or extracting it to a separate class will be required.


Best Practices

  1. Test through public methods: This validates the behavior rather than the implementation details.
  2. Refactor private methods: Extract complex logic into package-private or helper classes.
  3. Avoid overusing reflection: Reflection breaks encapsulation and makes tests fragile.
  4. Mock dependencies: Use mocking tools like Mockito for private fields or behaviors.

Summary

What to TestSolution
Private MethodsTest indirectly via public methods.
Complex Private MethodsRefactor into a helper class or package-private.
Private FieldsUse reflection or mocking to inject dependencies.
Private Inner ClassesTest via the outer class or use reflection.

Testing private members directly is generally discouraged because private code is part of the implementation, and tests should focus on observable behavior via public interfaces. 🚀


Course illustration
Course illustration

All Rights Reserved.