How to override #PropertySource which uses Json file with JsonPropertySourceFactory? - java

I have a configuration file as follows
#Configuration
#PropertySource(
value = "file:${resourcePath}/../Config/config.json",
factory = JsonPropertySourceFactory.class,
ignoreResourceNotFound = true)
#ConfigurationProperties
public class EmpRequest {
private String empName;
private String empPwd;
private String empXnum;
private boolean empProtected;
/*Getters setters */
}
and to test this configuration pojo, below is the test class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = EmpRequest.class)
#TestPropertySource(locations = "classpath:config.json")
public class EmpRequestTest {
#Autowired
private EmpRequest empRequest;
#Test
public void testEmpService() {
System.out.println(empRequest.getEmpName());
}
}
But this override is not working. The sysout in test class is printing null.
Looks like the problem is to invoke JsonPropertySourceFactory with the overridden config file. But when I debug, the JsonPropertySourceFactory is still getting invoked on file:${resourcePath}/../Config/config.json
Any clues on this.

Related

How can I use BeanFactory.getBean() in a test class field?

I'm writing test cases in which I would like to use the same BeanFactory.getBean() instance with only small changes - so I don't want to write e.g.: Class class = factory.getBean("someName", Class.class); every single time, but I'd rather just put it in a private field and use the class variable in the various test cases.
The test class looks like this:
#ExtendWith(SpringExtension.class)
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles(value = "test")
#Import(TestConfiguration.class)
public class FeedbackServiceTest {
#Autowired
private BeanFactory beanFactory;
//I'd like to use this in the various methods:
private AppUser testSender = beanFactory.getBean("sender", AppUser.class);
//other fields to be cleared up with BeanFactory:
private final FeedbackRepository feedbackRepository = Mockito.mock(FeedbackRepository.class);
private final AppUserRepository appUserRepository = Mockito.mock(AppUserRepository.class);
private final ValueRepository valueRepository = Mockito.mock(ValueRepository.class);
private final FeedbackService feedbackService = new FeedbackServiceImpl(feedbackRepository, appUserRepository,
valueRepository);
private final AppUser testTarget = new AppUser("someUserDatas"));
private final Value testValue = new Value("someValueDatas");
private final Feedback testFeedback = new Feedback(testSender, testValue, testTarget, "otherDatas");
#Test
public void saveNewFeedback_WithValidParams_ShouldSaveFeedback() {
Mockito.when(appUserRepository.findById(testSender.getId()))
.thenReturn(Optional.of(testSender));
Mockito.when(appUserRepository.findById(testTarget.getId()))
.thenReturn(Optional.of(testTarget));
Mockito.when(valueRepository.findById(testValue.getId()))
.thenReturn(Optional.of(testValue));
feedbackService.saveNewFeedback(testFeedback);
Mockito.verify(feedbackRepository, times(1)).save(Mockito.any(Feedback.class));
}
And the TestConfiguration class is this:
#Configuration
public class TestConfiguration {
#Bean(name = "sender")
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public AppUser getAppUser() {
return new AppUser("Pista", "pista#pista.com", Gender.MALE, "p123_ABC", "USER",
LocalDate.parse("1967-10-12"));
}
}
If I put the private AppUser testSender = beanFactory.getBean("sender", AppUser.class); line in the method scope, it runs fine, otherwise it throws the glorious NullPointerException
If I understand you correctly, then your requirement is to use reference of testSender in each of method. For this I can suggest you to use #BeforeEach in FeedbackServiceTest
:
public class FeedbackServiceTest {
#Autowired
private BeanFactory beanFactory;
//I'd like to use this in the various methods:
private AppUser testSender;
#BeforeEach
public void setBean(){
testSender= beanFactory.getBean("sender", AppUser.class);
}
...//rest other class
& then use testSender in each of your test method.
Edit:
Alternatively, you can use Qualifier bean if your TestConfiguration is correctly creating bean (Here no need to use #BeforeEach in this case):
public class FeedbackServiceTest {
#Autowired
#Qualifier(value="sender")
private AppUser testSender;
...

How to inject value to a bean in spring test?

i have a question here, please give some ideas.
I have two beans. FaceComparisonServerImpl depends on FaceServer.
When i want to test. I want to change the String in my 'FaceServer' bean.
#Service
public class FaceComparisonServerImpl implements FaceComparisonServer {
#Autowired
private FaceServer faceServer;
#Override
public FaceComparsionInfo getServerInfo() {
String serverInfo = faceServer.getServerInfo();
...
}
}
#Component
public class FaceServer {
#Autowired
private RestTemplate restTemplate;
//Not final, just to test.
private String version = "1.0";
private static final String CODE = "code";
private static final String MESSAGE = "message";
//Final
private static final String SERVER_URL = "http://127.0.0.1:8066/api/ZKComparison";
}
Bellow is my test code.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfig.class)
public class FaceServerTestByTyler {
#Autowired
private FaceComparisonServer faceComparisonServer;
#Test
public void getServerInfo(){
//How can i modify the value of SERVER_URL in faceServer?
FaceComparsionInfo serverInfo = faceComparisonServer.getServerInfo();
System.out.println(serverInfo);
}
}
My question is:
How can i modified the value of 'version' and 'SERVER_URL' in #Bean(faceServer)?
Thanks you!
You need create FaceServer mock bean for test configuration.
And override required methods
#Configuration
Class TestConfig{
#Bean
#Primary
public FaceServer faceServer() {
return new FaceServer() {
#override
public String getServerInfo(){
return "required info";
}
};
}
}
The easiest way to customize the values is to make them Spring properties:
#Component
public class FaceServer {
#Value("${faceServer.version}")
private String version;
#Value("${faceServer.url}")
private String serverUrl;
// ...
}
You can either have default values for the #Value annotations or use some default property values in application.yml.
Now just override those properties in your test with the values you want:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfig.class)
#TestPropertySource(properties = {
"faceServer.version=1.0",
"faceServer.url=http://127.0.0.1:8066/api/ZKComparison"
})
public class FaceServerTestByTyler {
#Autowired
private FaceComparisonServer faceComparisonServer;
// ...
}
However...
The second option is to make your classes more unit-testable. Prefer construction injection over field injection, and you can test your classes more independently.
#Service
public class FaceComparisonServerImpl implements FaceComparisonServer {
private final FaceServer faceServer;
public FaceComparisonServerImpl(FaceServer faceServer) {
this.faceServer = faceServer;
}
#Override
public FaceComparsionInfo getServerInfo() {
String serverInfo = faceServer.getServerInfo();
// ...
}
}
This now becomes unit-testable:
public class FaceServerTestByTyler {
private FaceComparisonServer faceComparisonServer;
private FaceServer faceServer;
#BeforeEach
public setup() {
faceServer = mock(FaceServer.class);
faceComparisonServer = new FaceComparisonServer(faceServer);
}
#Test
public void getServerInfo() {
when(faceServer.getServerInfo()).thenReturn(xxx);
// ...
}
}
The second option ends up with a test that runs much faster than any solutions that suggest to create a mock bean through a test configuration.

ConfigurationProperties doesn't work when different prefixes and same values are used

I have the following yaml-property file:
myPrefix:
value: Hello
myPrefix2:
value: World
And two classes
#PropertySource("classpath:permission-config.yml")
#ConfigurationProperties(prefix = "myPrefix")
#Component
#Getter
#Setter
public class ViewUsers {
private String value;
}
and
#PropertySource("classpath:permission-config.yml")
#ConfigurationProperties(prefix = "myPrefix2")
#Component
#Getter
#Setter
public class ManageUsers {
private String value;
}
Then null is injected.
Or, if I try to use #Value then ONLY latest value is retrieved, which is the last one (World), the preceding ones are always ignored.
Try the following approach:
Remove #Component from configuration properties (ViewUsers and ManageUsers)
Instead, use the following construction:
#PropertySource("classpath:permission-config.yml")
#ConfigurationProperties(prefix = "myPrefix")
public class ViewUsers {
private String value; // getter, setter
}
#PropertySource("classpath:permission-config.yml")
#ConfigurationProperties(prefix = "myPrefix2")
public class ManageUsers {
private String value; // getter, setter
}
#Configuration
#EnableConfigurationProperties({ViewUsers.class, ManageUsers.class}
public class MySampleConfiguration {
... beans here...
}
Also make sure that Lombok annotations are working as expected (Try to use without lombok just for the POC).
Update 1
As #M. Deinum has kindly stated, PropertySource like this doesn't work with yaml files.
You can try the following workaround:
#Configuration
#PropertySource(name = "someName", value = "classpath:foo.yaml", factory = YamlPropertySourceFactory.class)
public class MyConfig {
}
import org.springframework.boot.env.YamlPropertySourceLoader;
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
final List<PropertySource<?>> load = new YamlPropertySourceLoader().load(name, resource.getResource());
return load.get(0);
}
}

Spring-Boot multi module project load property-file

I have a Spring-Boot-Application as a multimodule-Project in maven. The structure is as follows:
Parent-Project
|--MainApplication
|--Module1
|--ModuleN
In the MainApplication project there is the main() method class annotated with #SpringBootApplication and so on. This project has, as always, an application.properties file which is loaded automatically. So I can access the values with the #Value annotation
#Value("${myapp.api-key}")
private String apiKey;
Within my Module1 I want to use a properties file as well (called module1.properties), where the modules configuration is stored. This File will only be accessed and used in the module. But I cannot get it loaded. I tried it with #Configuration and #PropertySource but no luck.
#Configuration
#PropertySource(value = "classpath:module1.properties")
public class ConfigClass {
How can I load a properties file with Spring-Boot and access the values easily? Could not find a valid solution.
My Configuration
#Configuration
#PropertySource(value = "classpath:tmdb.properties")
public class TMDbConfig {
#Value("${moviedb.tmdb.api-key}")
private String apiKey;
public String getApiKey() {
return apiKey;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Calling the Config
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
public TMDbWarper(){
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
I'm getting an NullPointerException in the constructor when I autowire the warper.
For field injection:
Fields are injected right after construction of a bean, before any config methods are invoked. Such a config field does not have to be public. Refer Autowired annotation for complete usage. Use constructor injection in this case like below:
#Component
public class TMDbWarper {
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
#Autowired
public TMDbWarper(final TMDbConfig tmdbConfig){
this.tmdbConfig = tmdbConfig;
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
(or)
Use #PostConstruct to initialise like below:
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
#PostConstruct
public void init() {
// any initialisation method
tmdbConfig.getConfig();
}
Autowiring is performed just after the creation of the object(after calling the constructor via reflection). So NullPointerException is expected in your constructor as tmdbConfig field would be null during invocation of constructor
You may fix this by using the #PostConstruct callback method as shown below:
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
public TMDbWarper() {
}
#PostConstruct
public void init() {
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
public TmdbApi getTmdbApi() {
return this.tmdbApi;
}
}
Rest of your configuration seems correct to me.
Hope this helps.
Here is a Spring Boot multi-module example where you can get properties in different module.
Let's say I have main application module, dataparse-module, datasave-module.
StartApp.java in application module:
#SpringBootApplication
public class StartApp {
public static void main(String[] args) {
SpringApplication.run(StartApp.class, args);
}
}
Configuration in dataparse-module. ParseConfig.java:
#Configuration
public class ParseConfig {
#Bean
public XmlParseService xmlParseService() {
return new XmlParseService();
}
}
XmlParseService.java:
#Service
public class XmlParseService {...}
Configuration in datasave-module. SaveConfig.java:
#Configuration
#EnableConfigurationProperties(ServiceProperties.class)
#Import(ParseConfig.class)//get beans from dataparse-module - in this case XmlParseService
public class SaveConfig {
#Bean
public SaveXmlService saveXmlService() {
return new SaveXmlService();
}
}
ServiceProperties.java:
#ConfigurationProperties("datasave")
public class ServiceProperties {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
application.properties in datasave-module in resource/config folder:
datasave.message=Multi-module Maven project!
threads.xml.number=5
file.location.on.disk=D:\temp\registry
Then in datasave-module you can use all your properties either through #Value.
SaveXmlService.java:
#Service
public class SaveXmlService {
#Autowired
XmlParseService xmlParseService;
#Value("${file.location.on.disk: none}")
private String fileLocation;
#Value("${threads.xml.number: 3}")
private int numberOfXmlThreads;
...
}
Or through ServiceProperties:
Service.java:
#Component
public class Service {
#Autowired
ServiceProperties serviceProperties;
public String message() {
return serviceProperties.getMessage();
}
}
I had this situation before, I noticed that the properties file was not copied to the jar.
I made the following to get it working:
In the resources folder, I have created a unique package, then stored my application.properties file inside it. e.g: com/company/project
In the configuration file e.g: TMDBConfig.java I have referenced the full path of my .properties file:
#Configuration
#PropertySource("classpath:/com/company/project/application.properties")
public class AwsConfig
Build and run, it will work like magic.
You could autowire and use the Enviornment bean to read the property
#Configuration
#PropertySource(value = "classpath:tmdb.properties")
public class TMDbConfig {
#Autowired
private Environment env;
public String getApiKey() {
return env.getRequiredProperty("moviedb.tmdb.api-key");
}
}
This should guarantee that property is read from the context when you invoke the getApiKey() method regardless of when the #Value expression is resolved by PropertySourcesPlaceholderConfigurer.

spring-boot properties not #Autowired

I am trying to get a Spring-boot application going and I am not sure what I am doing wrong here. I have a application.properties file at src/main/resources & src/test/resources. I have an #Bean for my ConfigurationSettings so that I can use them throughout my application:
#Component
public class ConfigurationSettings {
private String product;
private String version;
private String copyright;
private String appName;
private String appDescription;
...
// getters and setters
}
Here is how I kick the application off:
#Configuration
#EnableJpaRepositories
#EnableAutoConfiguration
#EnableConfigurationProperties
#PropertySources(value = {#PropertySource("classpath:application.properties")})
#ComponentScan(basePackages = "com.product")
#EnableScheduling
public class OFAC {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run( OFAC.class, args );
}
And here is my configuration class:
#Configuration
#ComponentScan(basePackages = {"com.product"})
#PropertySources(value = {#PropertySource("classpath:application.properties")})
public class OFAConfiguration {
#Autowired
private Environment env;
#Bean
public ConfigurationSettings configurationSettings() {
ConfigurationSettings configurationSettings = new ConfigurationSettings();
configurationSettings.setAppDescription( env.getRequiredProperty("app.description" ) );
configurationSettings.setAppName( env.getRequiredProperty( "app.name" ) );
configurationSettings.setServerPort( env.getRequiredProperty( "server.port" ) );
return configurationSettings;
}
I am trying to use it in a controller:
#RestController
public class AboutController {
#Autowired
private ConfigurationSettings configurationSettings;
#RequestMapping(value = "/about", method = RequestMethod.GET)
public About index() {
String product = configurationSettings.getProduct();
String version = configurationSettings.getVersion();
String copyright = configurationSettings.getCopyright();
return new About( product, version, copyright );
}
}
However, when step thru this, all the values of ConfigurationSettings are null. I do have a test that successfully loads the values:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {OFAConfiguration.class})
public class OFAConfigurationTest {
#Autowired
private Environment environment;
#Autowired
private ConfigurationSettings configurationSettings;
#Test
public void testConfigurationLoads() {
assertNotNull(environment);
Assert.assertNotNull(configurationSettings);
}
#Test
public void testConfigurationSettingValues() {
assertEquals("Product Name", configurationSettings.getProduct());
assertEquals("0.0.1", configurationSettings.getVersion());
assertEquals("2014 Product", configurationSettings.getCopyright());
}
Can anyone see why the ConfigurationSettings are not being populated in my Controller?
Your configuration leads to 2 instances of the ConfigurationSettings class and probably one instance overrides the other.
The 'ConfigurationSettings' has the #Component annotation as you are scanning for components (#ComponentScan) this will lead to an instance. You also have a #Bean annotated method which also leads to an instance. The latter is overridden with the first.
In short remove the #Component annotation as that isn't needed because you already have a factory method for this class.
public class ConfigurationSettings { ... }
You should also remove the #PropertySource annotations as Spring-Boot will already load the application.properties for you.
Finally you should not use the #ContextConfiguration annotation on your test class but the #SpringApplicationConfiguration and pass in your application class (not your configuration class!).
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes=OFAC.class)
public class OFAConfigurationTest {
#Autowired
private Environment environment;
#Autowired
private ConfigurationSettings configurationSettings;
#Test
public void testConfigurationLoads() {
assertNotNull(environment);
assertNotNull(configurationSettings);
}
#Test
public void testConfigurationSettingValues() {
assertEquals("Product Name", configurationSettings.getProduct());
assertEquals("0.0.1", configurationSettings.getVersion());
assertEquals("2014 Product", configurationSettings.getCopyright());
}
This will fix your runtime configuration problems and will let your test use the power of Spring Boot to configure your application.

Categories

Resources