I am trying to run an integration test using @SpringBootTest involving a service class which makes use of another component class. The component class (let's call it Component1) has field values that are set using @Value from the application.properties file (located in src/main/resources). If I autowire Component1 and test it directly, the properties load just fine. The trouble comes when I go up a level and test my service class which uses the component. In this case, I autowire my service class (let's call it Service1) and call it, and it calls Component1 to do some work for it. When that happens, Component1 comes up with all @Value fields null and cannot do its job.
There are a few threads here that ask a similar question regarding properties not loading, but none of the proposed solutions work for me.
Also, not sure if it is relevant to the issue, but Component1 is part of a factory pattern, so there is a factory class that instantiates and hands out abstract GenericComponent classes, and Component1 is subclassed from that.
Component1 looks something like this:
@Component("Component1")
public class Component1 extends GenericComponent {
@Value("${myValueFromPropertiesFile}")
private String MY_VALUE_FROM_PROPERTIES_FILE;
//... other values loaded from properties file
@Override
public List<OtherData> doSomething(MyData myData) {
// ... Process myData
}
}
The application.properties file is located in src/main/resources and looks something like this.
# Other values above
myValueFromPropertiesFile=123456
databaseProperty1=blahblahblah
otherServiceClassProperty2=otherotherother
otherValue3=789
# Other values below
Service1 looks like this. It makes use of the factory to return the correct instance of GenericComponent, which in this case is Component1.
@Service
@RequiredArgsConstructor
public class Service1 {
// MyFactory will look at myData and return the correct instance
// of GenericComponent
private final MyFactory myFactory;
public List<OtherData> processData(MyData myData) {
GenericComponent genericComponent = myFactory.getGenericComponent(myData);
return genericComponent.doSomething(myData);
}
//... other fields and methods
}
This is my test class that works just fine. It autowires and tests Component1 directly. If I run this test, Component1 receives all the @Value properties and everyone is happy.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class Component1IntegrationTest {
@Autowired
private Component1 component1;
// ... test methods
}
Here is the troublemaker - my test class for testing Service1. When I run this test, Component1 gets null for all @Value fields.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class Service1IntegrationTest {
@Autowired
Service1 service1;
//... test methods
}
Furthermore, from the logs, I know that the context is loading application.properties because the database properties are being loaded correctly. Yet none of the properties are read in to Component1 - all @Value fields are null.
I have tried adding @PropertySource("classpath:application.properties") to the test class, to the component, to the service, and to all of them. It did not help.
I also went down a rabbit hole of trying to create a configuration class specifically for this test, but that only seems to make matters worse with undefined beans. If I leave out the configuration, the application runs and then Component1 cannot do what it needs due to the null fields.
The only other solution I can think of is to resort to passing all Component1's fields in via constructor, but I really don't want to do that when reading them from the properties file makes the most sense since they're constants and not needed by any other parts of the application.
Does anyone see anything I have missed? My instincts are telling me the factory pattern could possibly be messing up the works, but I can't find anything indicating how a factory pattern would prevent the context from passing along the right properties. I'm stumped.
EDIT: I understand there are some problems with using the factory design pattern within spring. Believe me, I would love to have spring handle everything for me, but in this case, the factory pattern is critical to the application. To give a little background, the GenericComponents are actually handlers for communicating with different external REST APIs. This system has to be able to talk with different APIs, and the way I am doing this is through using a factory to examine the data, determine which API is the right one to talk to given the data contents, and having the factory return the correct instance of GenericComponent that knows how to talk to whatever API is needed. I did have to do some groundwork to get the factory pattern to play nicely with Spring, and it seems to have worked up until now with the properties issue.