M01 Q17 How to access private fields in tests?
So to inject something into the private field you can do two things.
Injection of dependency into the private field with @Autowired
In this example, the challenge with the testing is that if you have a private field then you cannot modify it. The issue is that we cannot inject the mock since we do not have access to it.
Injection of property into the private field with @Value
In the second example, we have a property with private fieild and we don't have control over it from the test level.
How to access the private field in testing?
Private Field cannot be accessed from outside of the class, to resolve this when writing Unit Test you can use the following solutions:
- Use
SpringRunnerwithContextConfigurationand@MockBean.This will create a context with production code. - Use
ReflectionTestUtilsto modify private fields - Use
MockitoJUnitRunnerto inject mocks. And Mockito will automatically find a way to inject the fields based on the types. - Use
@TestPropertySourceto inject test properties into private fields
Now let's go into the code.
For example, we have the ReportService and it has simple responsibility. It creates a report and then passes the report to ReportWriter. ReportWriter has only one method write(). So ReportWriter is a dependency and it comes from outside.
And also we have a property that is called reportGlobalName. It will be injected based on the property defined in the resources.
And now we want to test this service. The challenge is the private field. We don't have access to it. And here spring-boot-test or spring-test modules will help us.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>We can use @RunWith(SpringRunner.class) to create a context from the production code. And then instead of creating real dependency, we will inject the mock bean of ReportWriter.
The next solution to this problem is to use Mockito Runner @RunWith(MockitoJUnitRunner.class). Mockito doesn't create an instance of the Spring Context so it is a bit lighter. And also Mockito will inject mocks automatically based on the type.
The third solution is to use ReflectionTestUtils. We create a ReportService and create a mock of ReportWriter. And then we set a field on this ReportService. That means we need to pass the name of the field to the mock that we have created.
But the issue with this test is that if somebody will modify the field in the ReportService class then this test will fail. When other solutions will work as expected.
How to test the property field?
For the test of the property, the Spring Test module provides us with the @TestPropertySource.
In my case, I am just setting the report.global.name to something that is coming from my test.