Spring Boot Testing - Using ReflectionTestUtils for Unit Testing

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/util/ReflectionTestUtils.html

Notes from Spring documentation:

ReflectionTestUtils is a collection of reflection-based utility methods for use in unit and integration testing scenarios.

There are often times when it would be beneficial to be able to set a non-public field, invoke a non-public setter method, or invoke a non-public configuration or lifecycle callback method when testing code involving, for example:

  1. ORM frameworks such as JPA and Hibernate which condone the usage of private or protected field access as opposed to public setter methods for properties in a domain entity.
  2. Spring’s support for annotations such as @Autowired, @Inject, and @Resource which provides dependency injection for private or protected fields, setter methods, and configuration methods.
  3. Use of annotations such as @PostConstruct and @PreDestroy for lifecycle callback methods.

Set a Value of a Non-Public Field

@Test
public void whenNonPublicField_thenReflectionTestUtilsSetField() {
    // Employee class has a private field called "id"
    Employee employee = new Employee();
    ReflectionTestUtils.setField(employee, "id", 1);

    assertTrue(employee.getId().equals(1));
}

Invoke a Non-Public Method

Private method in the class that needs to be tested

private String employeeToString(){
    return "id: " + getId() + "; name: " + getName();
}

Testing it

@Test
public void whenNonPublicMethod_thenReflectionTestUtilsInvokeMethod() {
    Employee employee = new Employee();
    ReflectionTestUtils.setField(employee, "id", 1);
    employee.setName("Smith, John");

    assertTrue(ReflectionTestUtils.invokeMethod(employee, "employeeToString")
      .equals("id: 1; name: Smith, John"));
}

Inject Dependencies

Class with a private injected dependency

@Component
public class EmployeeService {

    @Autowired
    private HRService hrService;

    public String findEmployeeStatus(Integer employeeId) {
        return "Employee " + employeeId + " status: " + hrService.getEmployeeStatus(employeeId);
    }
}

Dependency that is being injected

@Component
public class HRService {

    public String getEmployeeStatus(Integer employeeId) {
        return "Inactive";
    }
}

Mocking the injected class in the test class

HRService hrService = mock(HRService.class);
when(hrService.getEmployeeStatus(employee.getId())).thenReturn("Active");

Testing it

EmployeeService employeeService = new EmployeeService();
ReflectionTestUtils.setField(employeeService, "hrService", hrService);

Injecting “Value"s

public class MyClass {

    @Value("${value.from.property.file}")
    private String valueFromProperyFile;

    public void myMethod() {
        // change the value of valueFromProperyFile in this method
    }

}

Setting a dummy value to it before each test

public class MyClassTests {

    @Spy
    @InjectMocks
    MyClass myClass;

    @BeforeEach
    void initialize() {
        ReflectionTestUtils.setField(myClass, "valueFromProperyFile", "dummyValue")
    }

Testing the value of a non-public variable

@Test
public void myTestMethod() {
    assertDoesNotThrow -> (() -> myClass.myMethod());

    verify(myClass, times(1)).myMethod();

    assertEquals("changedValue", ReflectionTestUtils.getField(myClass, "valueFromProperyFile"));
}