Loading properties file from yml as Factory Bean in JUNIT - java

I have list of properties in my yml for eachplatform tv and android
i have created one factory class where we provide the platform and you will get the properties , using the referring link "https://andrewzc.com/loading-configuration-properties-in-spring-boot-test/" .
but on running the test the property object is coming as null . How to load properties in junit from properties file ?
#Component
public class MasterPropertiesFactory {
#Autowired
private TvProperties tv ;
#Autowired
private AndroidProperties android ;
public MasterPropertiesClass getProperty(String platform){
if(platform.equals("tv")){
return tv;
}else if(platform.equals("android")){
return android;
}
}
}
Now i am writing a junit to read these properties but on running test getPaginatedContent it is coming as null .
//PageTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {TestApp.class ,MasterPropertiesFactory.class }, initializers = ConfigFileApplicationContextInitializer.class)
class PageTest {
#Autowired
MasterPropertiesFactory masterPropertiesFactory;
#Test
void getPaginatedContent() {
Assert.assertNotNull(masterPropertiesFactory);
}
}
// TestApp.java
#EnableAutoConfiguration
#EnableConfigurationProperties(value = {MasterPropertiesFactory.class })
public class TestApp {}

There're several points you should fix in your code:
There should be Configuration class for creating TvProperties and AndroidProperties
Either you should use that class in PageTest - ContextConfigurationor you should declare custom Configuration class for the test, to load the properties.
#ContextConfiguration(classes = {TestApp.class ,MasterPropertiesFactory.class }is not good idea, because, it should contain information about Configuration classes.
For #Autowired
MasterPropertiesFactory masterPropertiesFactory; somewhere you should create bean for that (preferred way is in test Conifugration)

Related

Fail to inject #Value in tests with Spring, Spock & Groovy

I have problems with injection #Value('${mybean.secret}') property into my bean during tests in Spock with Spring Boot & Groovy.
I have a very simple test class MyBeanTest
#ContextConfiguration(classes = [
MyAppConfig
])
#PropertySource([
"classpath:context/default-properties.yml"
])
class MyBeanTest extends Specification {
#Autowired
MyBean myBean
def "should populate properties "() {
expect:
myBean.secretProperty == "iCantTellYou"
}
}
And MyAppConfig.groovy as this:
#Configuration
class MyAppConfig {
#Bean
MyBean credential(#Value('${mybean.secret}') String secret) {
return new MyBean(secret)
}
}
When I run tests the value that is injected into secret is simply ${mybean.secret}.
The real value is not injected from properties file I enclose on test specification.
I'm using single-quote on #Value because of Groovy. Double quote with $ sign makes it being processed by groovy GString mechanism.
However, the problem doesn't occur on regular application run.
If I start application and put the breakpoint on the MyAppConfig#credential method the secret value is correctly read from the properties file, which are configured as follow:
#Configuration
#PropertySource(["classpath:context/default-properties.yml"])
class PropertiesConfig {
}
When I specify property from hand like this:
#TestPropertySource(properties = [
"mybean.secret=xyz"
])
class MyBeanTest extends Specification {
It works. The property is read. But it's not my goal, cause there's much more properties in the project and it would become cumbersone to define them everywhere from hand.
Can you spot the problem I am missing in this code?
The missing puzzle was YamlPropertySourceFactory.
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
I was using yml properties files with nested properties:
mybean:
secret: xyz
And Spring was not loading them correctly.
I had to update #PropertySource annotation as well as follow:
#Configuration
#PropertySource(
value = ["classpath:context/default-properties.yml"],
factory = YamlPropertySourceFactory.class
)
class PropertiesConfig {
}
Now it works like a charm 😏.
I learned this on Baeldung website => https://www.baeldung.com/spring-yaml-propertysource

The injection point has the following annotations: - #org.springframework.beans.factory.annotation.Autowired(required=true)

I am new to Spring Boot and I'm getting the following error when writing a file upload API:
Error:Description:
Field fileStorageService in com.primesolutions.fileupload.controller.FileController required a bean of type 'com.primesolutions.fileupload.service.FileStorageService' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.primesolutions.fileupload.service.FileStorageService' in your configuration.*
Controller class:
public class FileController
{
private static final Logger logger = LoggerFactory.getLogger(FileController.class);
#Autowired
private FileStorageService fileStorageService;
#PostMapping("/uploadFile")
public UploadFileResponse uploadFile(#RequestParam("file") MultipartFile file) {
String fileName = fileStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(fileName)
.toUriString();
return new UploadFileResponse(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
#PostMapping("/uploadMultipleFiles")
public List<UploadFileResponse> uploadMultipleFiles(#RequestParam("files") MultipartFile[] files) {
return Arrays.asList(files)
.stream()
.map(file -> uploadFile(file))
.collect(Collectors.toList());
}
}
Service class:
private final Path fileStorageLocation;
#Autowired
public FileStorageService(FileStorageProperties fileStorageProperties) {
this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
public String storeFile(MultipartFile file) {
// Normalize file name
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
// Check if the file's name contains invalid characters
if(fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = this.fileStorageLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
Configuration class:
#ConfigurationProperties(prefix = "file")
public class FileStorageProperties {
private String uploadDir;
public String getUploadDir()
{
return uploadDir;
}
public void setUploadDir(String uploadDir) {
this.uploadDir = uploadDir;
}
}
Main:
#SpringBootApplication
#EnableConfigurationProperties({
FileStorageProperties.class
})
public class FileApplication {
public static void main(String[] args) {
SpringApplication.run(FileApplication.class, args);
}
}
properties file
## MULTIPART (MultipartProperties)
# Enable multipart uploads
spring.servlet.multipart.enabled=true
# Threshold after which files are written to disk.
spring.servlet.multipart.file-size-threshold=2KB
# Max file size.
spring.servlet.multipart.max-file-size=200MB
# Max Request Size
spring.servlet.multipart.max-request-size=215MB
## File Storage Properties
# All files uploaded through the REST API will be stored in this directory
file.upload-dir=C:/Projects/SpringBootProject/Primesolutions/PrimeSolutions/FileUpload
I'm trying to read the file upload property and pass it to the controller class.
The error seems to indicate that Spring does not know any bean of type com.primesolutions.fileupload.service.FileStorageService.
As said in the comment, make sure you class FileStorageServiceis annotated by #Service or #Component:
#Service
public class FileStorageService {
...
}
Make also sure that this class is located in a sub-package of your class FileApplication. For example, if your FileApplication class is located in a package com.my.package, make sure your FileStorageService is located in the package com.my.package.** (same package or any sub package).
Few notes to improve your code by the way :
When your class has only one not default constructor, the use of #Autowired on the constructor is optional.
Do not put too much code in your constructor. Use instead the #PostConstruct annotation.
#Service
public class FileStorageService {
private FileStorageProperties props;
// #Autowired is optional in this case
public FileStorageService (FileStorageProperties fileStorageProperties) {
this.props = fileStorageProperties;
this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
.toAbsolutePath().normalize();
}
#PostConstruct
public void init() {
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
}
It is better to avoid the #Autowired on a field. Use the constructor instead. It is better for your tests, and more maintainable:
public class FileController {
private FileStorageService service;
public FileController(FileStorageService service) {
this.service = service;
}
}
I solved this problem using where i use #Autowired annotation just replace with this`
#Autowired(required = false)
`
When #Autowired doesn’t work
There are several reasons #Autowired might not work.
When a new instance is created not by Spring but by for example manually calling a constructor, the instance of the class will not be registered in the Spring context and thus not available for dependency injection. Also when you use #Autowired in the class of which you created a new instance, the Spring context will not be known to it and thus most likely this will also fail.
Another reason can be that the class you want to use #Autowired in, is not picked up by the ComponentScan. This can basically be because of two reasons.
The package is outside the ComponentScan search path. Move the
package to a scanned location or configure the ComponentScan to
fix this.
The class in which you want to use #Autowired does not have a
Spring annotation. Add one of the following annotatons to the class:
#Component, #Repository, #Service, #Controller,
#Configuration. They have different behaviors so choose carefully!
Read more here.
I solved this problem using :
#ComponentScan({ "com.yourpkg.*" })
Make sure you #ComponentScan cover all classes contains annotatons : #Component, #Repository, #Service, #Controller, #Configuration.
Reference : https://technology.amis.nl/2018/02/22/java-how-to-fix-spring-autowired-annotation-not-working-issues/
Tried with removing the (exclude = {DataSourceAutoConfiguration.class }) parameter with #SpringBootApplication:
Before:
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class SpringBootMain { ...
After:
#SpringBootApplication
public class SpringBootMain { ...
Worked for me.
The class which is going to be Autowired should be marked with #Service or #Component. Also if the class is in different package then need to add the #ComponentScan annotation in the main class as follows.
#ComponentScan({"com.beta.replyservice", "com.beta.ruleService"})
#SpringBootApplication
Solution is
#Autowired(required = false)
private FileStorageService fileStorageService;
I had the same issue. It was solved for me when I added a dependency on "spring-webmvc".
When I had the same problem, I just added a default constructor in my service class and it started working.
=> Error should look like this:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field accountPresentation in june14th.account.TestSpringBoot required a bean of type 'june14th.controller.AccountPresentation' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'june14th.controller.AccountPresentation' in your configuration.
Solution to the problem:-
First and most important step is look whether your code has the needed dependencies.
Check your Folder structure (this error is mainly due to this problem, always check your package structure) i.e, check wheather the main package and its sub packages are well structured
eg:
package X.account; //consider this is my main file package
package X.controller; //consider this as my presentation file package
when you run this program this will cause our "APPLICATION FAILED TO START" error, because of our package structure..look
package X.account; //main file package [the next fie should inside this package i.e as a sub package]
package.X.account.controller // this is the right way
I think this should solve your problem.
Put #Autowired(required=true) //do only if above will not work
Make sure to have respective annotations for classes. The same issue got solved for me when I add #Service annotation for interfaces and implemented service classes.
I use #Service on the service class which has to be Autowired. It solves my error. or you can use #Autowired(required = false) to disable the auto wiring for a particular instance.

Overriding #Value in Integration Test

For one of my Spring beans(say Application class), I'm fetching the value of a property(my.property.flag=true/false) from a properties file(prop.properties) using #Value annotation. That works perfectly fine.
I need to write an integration test(say ApplicationIt class) where I need to test with both the values of the property i.e. for both true and false.
In my properties file, the value of the property is set to true. Is it possible to set the value dynamically to false from my Integration test?
For Example,
prop.properties:
my.property.flag=true
Application class file:
#Component
class Application {
//This value is fetched from properties file
//the value is set to true.
#Value(${my.property.flag})
private String isTrue;
......
..........
}
Integration Test:
class ApplicationIT {
//how can I set the value of isTrue here to false?
}
You can specify test properties on the test class as follows:
#RunWith(SpringRunner.class)
#TestPropertySource(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
public class MyTest {
Since Spring has a whole hierarchy of property overrides, this works pretty well, the downside being you need separate test classes for different values. If you're using Spring Boot, there's another annotation that provides the same functionality but also has more options for configuring your test environment. Example:
#SpringBootTest(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
Again, you will need separate test classes to handle hard-coded test properties.
I was bugged with this for a while and found this neat way to override the properties. It is quite useful if you need some programmatic initialization of the application context such as registering property sources like in that case but not only. The following approach uses ContextConfiguration's initializers.
example for Spring Boot 1.5.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(ctx,
"spring.redis.host=" + redis.getContainerIpAddress(),
"spring.redis.port=" + redis.getMappedPort(REDIS_PORT));
}
}
}
example for Spring Boot 2.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertyValues.of(
"spring.redis.host:" + redis.getContainerIpAddress(),
"spring.redis.port:" + redis.getMappedPort(REDIS_PORT))
.applyTo(ctx);
}
}
}
I want to mention good old reflection way. You can use spring provided utility class for it after you wired in your component:
ReflectionTestUtils.setField(component, "isTrue", true)
You can change it to any value you want in consequent tests
Preferably, use constructor injection instead of field injection:
#Component
class Application {
Application(#Value("${my.property.flag}") boolean flag) {
...
}
}
This makes using mocks or test values as simple as passing an argument.

Should I mock Spring Cloud Config server properties during tests?

How do I test a service that has properties from spring cloud config server injected into it as a dependency?
-Do I simply create my own properties during testing using the new keyword?(new ExampleProperties())
Or do I have to use spring and create some kind of test properties and use profiles to tell which properties to use?
Or should I just let spring call the spring cloud config server during testing?
My service looks like the one below:
#Service
class Testing {
private final ExampleProperties exampleProperties
Testing(ExampleProperties exampleProperties) {
this.exampleProperties = exampleProperties
}
String methodIWantToTest() {
return exampleProperties.test.greeting + ' bla!'
}
}
My project makes a call to a spring cloud config server during start up to get properties, this is enabled by having the following on the bootstrap.properties:
spring.cloud.config.uri=http://12.345.67.89:8888
I have a configuration that looks like the one below:
#Component
#ConfigurationProperties
class ExampleProperties {
private String foo
private int bar
private final Test test = new Test()
//getters and setters
static class Test {
private String greeting
//getters and setters
}
}
The properties file looks like this:
foo=hello
bar=15
test.greeting=Hello world!
You can use #TestPropertySource annotation to fake properties during test:
#ContextConfiguration
#TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
public class MyIntegrationTests {
// class body...
}
For Unit test just simply mock Properties and use Mockito methods when(mockedProperties.getProperty(eq("propertyName")).thenReturn("mockPropertyValue") and it will be fine.
For Integration test all Spring context should be inited and work as regular app, in that case you dont need to mock your properties.
Another option is to use properties attribute of SpringBootTest annotation:
#SpringBootTest(properties = {"timezone=GMT", "port=4242"})

Defining a spring active profile within a test use case

Using Spring 4, I've got the following test setup:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = JpaConfig.class)
#ActiveProfiles(resolver = TestResolver.class)
public class SimpleTest {
The TestResolver has been implemented as:
public class TestResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> aClass) {
String[] profiles = new String[1];
profiles[0] = "test";
return profiles;
}
}
JpaConfig has been annotated with PropertySource
#Configuration
#PropertySource("classpath:properties/application-${spring.profiles.active:dev}.properties")
#EnableJpaRepositories(basePackages={"com.my.namespace.repositories"})
public class JpaConfig {
Whenever I run the SimpleTest it tries to locate: properties/application-dev.properties while I expected it to be properties/application-test.properties.
What's I'm trying to accomplish here has been based on the following post: Spring integration tests with profile
I believe this is, actually, the issue you are facing. And in that same post you have an explanation from Dave Syer and a possible solution from another user. To follow Dave's advice, this would be a possible implementation of an ApplicationContextInitializer:
public class MyApplicationContextInitializer implements
ApplicationContextInitializer<GenericApplicationContext> {
public void initialize(GenericApplicationContext context) {
context.getEnvironment().getSystemProperties().put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "some_profile");
}
}
and on your test class:
#ContextConfiguration(classes = JpaConfig.class, initializers = MyApplicationContextInitializer.class)
But I would say that the suggested approach (with different .properties files loaded for different profiles) in that SO post is a more elegant approach.
I think you should change #PropertySource to:
#PropertySource("classpath:properties/application-${spring.profiles.active}.properties")
Also for simplicity (shouldn't have any effect on how the code runs) your #ActiveProfile could be
#ActiveProfiles("test")

Categories

Resources