Spring boot: #ConfigurationProperties not satisfied on test - spring-boot

I'm getting this exception on my test excution:
UnsatisfiedDependencyException: Error creating bean with name 'net.gencat.transversal.espaidoc.mongo.GridFSTest': Unsatisfied dependency expressed through field 'resourceProperties'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'net.gencat.transversal.espaidoc.ResourcesConfigProperties' available: expected at least 1 bean which qualifies as autowire candidate.
So, I think message is so clear enough: ResourcesConfigProperties is not satisfied.
My test:
RunWith(SpringRunner.class)
#SpringBootTest()
public class GridFSTest {
#Autowired
private GridFsTemplate gridFsTemplate;
#Autowired
private ResourcesConfigProperties resourceProperties;
public URL getHugeResource() {
try {
return Paths
.get(this.resourceProperties.getHuge())
.toUri()
.toURL();
} catch (MalformedURLException e) {
return null;
}
}
#Test
public void storeHugeFile() throws IOException {
URL resource = this.getHugeResource();
this.gridFsTemplate.store(
resource.openStream(),
resource.getPath(),
"mime"
);
}
}
and ResourcesConfigProperties is:
#ConfigurationProperties(prefix = "files")
public class ResourcesConfigProperties {
private String huge;
/**
* #return the huge
*/
public String getHuge() {
return huge;
}
/**
* #param huge the huge to set
*/
public void setHuge(String huge) {
this.huge = huge;
}
}
into my src/test/resources I have my application.properties file:
files.huge: /home/jcabre/Downloads/1GB.zip
Any ideas?
EDIT
Main Spring boot application:
#SpringBootApplication(
//scanBasePackages = { "cat.gencat.ctti.canigo.arch.web.rs" },
exclude = JmxAutoConfiguration.class
)
#EnableConfigurationProperties({
ApiProperties.class,
FileStoreProperties.class
})
#Import(RedisConfiguration.class)
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}

TL;DR:
It is happening, because the #ConfigurationProperties are not managed by the application context you build in tests, although they will be load when the application launches, because you have #EnableConfigurationProperties on your app main class.
#EnableConfigurationProperties on main class only affect the application context you have when you bootRun, but not that in a #SpringBootTest. The application context you build in tests could be, under many circumstances, distinct with that of bootRun, just like in your case.
You can add #Component to make the context be aware of it, both in gradle bootRun and in gradle test. It's the easiest way, but not 100% the suggested way.
More proper way with details if you have time
Instead, you can add #EnableConfigurationProperties({Config1.class, Config2.class}) in a #SpringBootTest, to inject only some of the configuration properties class into the context, to avoid injection overhead.
It would be like:
//#RunWith(SpringRunner.class) // for JUnit4 in Spring
#ExtendWith(SpringExtension.class) // for JUnit5 in Spring.
#SpringBootTest
#EnableConfigurationProperties({
ResourcesConfigProperties.class,
})
#Data
public class ConfigsTest {
#Autowired
private ResourcesConfigProperties resourceConfigProperties;
...
}
Better yet, you can use #SpringBootTest(classes={}): classes within {} are those you want the application context of #SpringBootTest to manage(creation, initialization, loading properties from yaml files, and so on). Then you don't have to load all the context, but only part of it.
You can group all classes of #ConfigurationProperties in one class of #Configuration, and put it in the classes={} of #SpringBootTest, instead of repeating this list of #ConfigurationProperties everywhere. Something like:
//#RunWith(SpringRunner.class) // for JUnit4 in Spring
#ExtendWith(SpringExtension.class) // for JUnit5 in Spring.
#SpringBootTest(classes = {
TestConfiguration.class
})
#Data
public class ConfigsTest {
#Autowired
private ResourcesConfigProperties resourceConfigProperties;
...
}
TestConfiguration.java:
#EnableConfigurationProperties({
ResourcesConfigProperties.class,
})
#Configuration
public class TestConfiguration {
}

You need to add ResourcesConfigProperties to your EnableConfigurationProperties annotation in the main spring boot class, this will load and create a bean out of the ResourceConfigProperties for you
You could also add #Component to your ResourceConfigProperties if you do not want to add it to the EnableConfigurationProperties annotation.
When using the SpringBootTest or any slice test it will use whatever is annotated on, or beans defined within the main SpringBootApplication within the test context.

You also need to annotate ResourcesConfigProperties class with #Configuration as below, otherwise it will not create a bean of this class in the spring container.
#Configuration
#ConfigurationProperties(prefix = "files")
public class ResourcesConfigProperties {
private String huge;
/**
* #return the huge
*/
public String getHuge() {
return huge;
}
/**
* #param huge the huge to set
*/
public void setHuge(String huge) {
this.huge = huge;
}
}

Related

Autoconfigure ReactiveCrudRepos in integration tests

I'm having some difficulty in writing some Integration tests. I have something like so
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Service.class)
public class ServiceTest {
#Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();
#Autowired
Service Service;
#Before
public void setUp() {
PostgresqlConnectionFactory connectionFactory = new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder()
.host(postgres.getHost())
.port(postgres.getFirstMappedPort()) // optional, defaults to 5432
.username(postgres.getUsername())
.password(postgres.getPassword())
.database(postgres.getDatabaseName()) // optional
.build());
Resource resource = new ClassPathResource("sql.sql");
Mono<PostgresqlConnection> mono = connectionFactory.create();
mono.map(connection -> connection
.createStatement(Helpers.asString(resource))
.execute()).block();
}
#Test
public void test() {
Request Request = new Request();
request.setName("name");
Mono<Item> itemMono = Service.createNewHub(hubRequest);
Item item = itemMono.block();
Assert.assertEquals(1L, 1L);
}
}
And my Service.class looks like the below
#Service
public class Service {
private Repository repository;
public Service(Repository repository) {
this.repository = repository;
}
public Flux<Item> getAllItems() {
return repository.findAll();
}
}
And my repo
#Repository
public interface Repository extends ReactiveCrudRepository<Item, Integer> {
}
My error is the following
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.Repository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
While all of the code I have written in the application is able to be injected fine, when it comes to the ReactiveCrudRepos, I am not having any luck on getting their instantiated object. What do I need to do to have the implementations created and injected?
As long as you use #SpringBootTest(classes = Service.class), the other beans are not loaded into the application context. The annotation element class of the annotation is described as:
The component classes to use for loading an ApplicationContext.
Remove the element and use the #SpringBootApplication as is:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Service.class)
public class ServiceTest { /* code */ }
Remember, using this way is testing an application context completely different from what will be run on the production environment. It should be a last resort.
Moreover, avoid naming the interface Repository when there exists the Spring annotation #Repository itself. I would personally prefer:
#Repository
public interface ItemRepository extends ReactiveCrudRepository<Item, Integer> {
}

ConstraintValidator dependency injection leads to ValidationException when being validated at class level

I've encountered an unexpected behaviour when using dependency injection in a ConstraintValidator which is getting evaluated at class level.
Entity class:
#Entity
#ValidDemoEntity
public class DemoEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
Validation annotation:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {DemoEntityValidator.class})
public #interface ValidDemoEntity {
String message() default "{some.demo.validator.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator:
public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity> {
private DemoEntityRepository demoEntityRepository;
public DemoEntityValidator(DemoEntityRepository demoEntityRepository) {
this.demoEntityRepository = demoEntityRepository;
}
#Override
public void initialize(ValidDemoEntity constraintAnnotation) {
}
#Override
public boolean isValid(DemoEntity demoEntity, ConstraintValidatorContext constraintValidatorContext) {
return true;
}
}
Test class:
#SpringBootTest
public class ValidatorInstantiationTest {
private Validator validator;
#Before
public void setUp() throws Exception {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.getValidator();
}
#Test
public void shouldInitiateAndCallDemoEntityValidator() {
DemoEntity demoEntity = new DemoEntity();
validator.validate(demoEntity);
}
}
Validating the entity leads to:
javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: com.example.demo.DemoEntityValidator.
and further down the stack trace:
Caused by: java.lang.NoSuchMethodException: com.example.demo.DemoEntityValidator.<init>()
which indicates that Hibernate tried to initiate the the class instead of letting Spring take care of that.
The strange thing about this is that dependency injection works fine for validations applied on field level.
The code is available at GitHub.
The exception says that there is no default constructor because Hibernate Validator tries to instantiate your validator.
You have to use Spring.
1 Make your validator a Spring Bean:
#Component
public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity> {
2 Inject the Spring provided validator and use the SpringRunner for executing your tests:
#SpringBootTest
#RunWith(SpringRunner.class)
public class ValidatorInstantiationTest {
#Autowired
private Validator validator;
#Test
public void shouldInitiateAndCallDemoEntityValidator() {
DemoEntity demoEntity = new DemoEntity();
validator.validate(demoEntity);
}
}
1 Make your validator a Spring Bean
This site states:
The Spring framework automatically detects all classes which implement the ConstraintValidator interface. The framework instantiates them and wires all dependencies like the class was a regular Spring bean.
Which clearly works for validations applied on field level.
Nevertheless I've updated the code.
DemoEntityValidator is now a Spring component:
#Component
public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity>
I've changed the test to:
#SpringBootTest
#RunWith(SpringRunner.class)
public class ValidatorInstantiationTest {
#Autowired
private DemoEntityRepository demoEntityRepository;
#Test
public void shouldInitiateAndCallDemoEntityValidator() {
DemoEntity demoEntity = new DemoEntity();
demoEntityRepository.save(demoEntity);
}
}
To make the usecase clearer, but the test still leads to the same exception.
Adding an empty constructor to the class DemoEntityValidator disables the error.
I think you answer is here:
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring
You need to declare a LocalValidatorFactoryBean in your configuration class and it will just work.
From the documentation:
By default, the LocalValidatorFactoryBean configures a
SpringConstraintValidatorFactory that uses Spring to create
ConstraintValidator instances. This lets your custom
ConstraintValidators benefit from dependency injection like any other
Spring bean.
And an example from the same place:
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
#Autowired;
private Foo aDependency;
...
}
And this is how I declared that bean in a #Configuration annotated class:
/**
* Provides auto-wiring capabilities for validators Checkout: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring
*/
#Bean
public LocalValidatorFactoryBean validatorFactoryBean() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(validationMessageSource());
return bean;
}
There's nothing wrong with your validator class. I got this working by making two changes to the test configuration:
1. Run test with Spring
In order to have Spring manage your beans, you need to run your test with a test runner that sets up Spring. You can specify the test runner class using junit's #RunWith-annotation:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ValidatorInstantiationTest { ... }
2. Inject a Spring managed validator bean
Since you're using Spring Boot, you can inject a Spring managed validator – it's already configured. This way, Spring will handle the initiation of your DemoEntityValidator.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ValidatorInstantiationTest {
#Autowired
private Validator validator;
...
}
This is all that is needed. You should not annotate your DemoEntityValidator with #Component or similar.
Note that you need to provide Spring with a data source, since SpringRunner will set up a context based on your Spring Boot setup (I'm guessing it includes spring-boot-starter-data-jpa in your case). The easiest way to get going is just to put an in-memory DB such as h2 on the classpath.

Multiple Spring Configuration files (one per Profile)

I'm a Spring rookie and trying to benefit from the advantages of the easy 'profile' handling of Spring. I already worked through this tutorial: https://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile and now I'd like to adapt that concept to an easy example.
I've got two profiles: dev and prod. I imagine a #Configuration class for each profile where I can instantiate different beans (implementing a common interface respectively) depending on the set profile.
My currently used classes look like this:
StatusController.java
#RestController
#RequestMapping("/status")
public class StatusController {
private final EnvironmentAwareBean environmentBean;
#Autowired
public StatusController(EnvironmentAwareBean environmentBean) {
this.environmentBean = environmentBean;
}
#RequestMapping(method = RequestMethod.GET)
Status getStatus() {
Status status = new Status();
status.setExtra("environmentBean=" + environmentBean.getString());
return status;
}
}
EnvironmentAwareBean.java
public interface EnvironmentAwareBean {
String getString();
}
EnvironmentAwareBean.java
#Service
public class DevBean implements EnvironmentAwareBean {
#Override
public String getString() {
return "development";
}
}
EnvironmentAwareBean.java
#Service
public class ProdBean implements EnvironmentAwareBean {
#Override
public String getString() {
return "production";
}
}
DevConfig.java
#Configuration
#Profile("dev")
public class DevConfig {
#Bean
public EnvironmentAwareBean getDevBean() {
return new DevBean();
}
}
ProdConfig.java
#Configuration
#Profile("prod")
public class ProdConfig {
#Bean
public EnvironmentAwareBean getProdBean() {
return new ProdBean();
}
}
Running the example throws this exception during startup (SPRING_PROFILES_DEFAULT is set to dev):
(...) UnsatisfiedDependencyException: (...) nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [EnvironmentAwareBean] is defined: expected single matching bean but found 3: prodBean,devBean,getDevBean
Is my approach far from a recommended configuration? In my opinion it would make more sense to annotate each Configuration with the #Profile annotation instead of doing it for each and every bean and possibly forgetting some variants when new classes are added later on.
Your implementations of EnvironmentAwareBean are all annotated with #Service.
This means they will all be picked up by component scanning and hence you get more than one matching bean. Do they need to be annotated with #Service?
Annotating each #Configuration with the #Profile annotation is fine. Another way as an educational exercise would be to not use #Profile and instead annotate the #Bean or Config classes with your own implementation of #Conditional.

Spring #Required properties when creating #Bean annotated beans

I'm developing a Spring Boot application and am trying out using Java annotation-based bean creation (using #Configuration and #Bean) rather than the familiar old XML-based bean creation. I'm puzzled though. If I attempt to create a bean in XML but fail to set an #Required property I get a BeanInitializationException when the application context is created. In my trials so far with annotation-based bean creation though this does not seem to be the case.
For example:
public class MyClass {
...
#Required
public void setSomeProp(String val){
}
}
Then in Spring XML:
<bean class="MyClass"/>
This will blow up during application startup (and IntelliJ flags it) because the required property is not set. But the same does not seem to be true of this:
#Configuration
public class MyConfig {
#Bean
public MyClass myClass() {
return new MyClass();
}
}
This application starts up just fine even though the required property is not ever set. I must be missing something here, because this seems like a pretty key feature in Spring.
UPDATE
I did some digging & debugging and it turns out that the bean definition is somehow being flagged to skip checking that #Required fields are set. In the Spring class 'RequiredAnnotationBeanPostProcessor' the boolean method 'shouldSkip()' is returning true for beans created this way. When I used the debugger to force that method to return false bean creation did indeed blow up with the expected exception.
Seeing as I'm making a pretty basic Spring Boot application I'm inclined (as Zergleb suggests) to submit this as a bug.
UPDATE 2
Some further debugging has revealed that even if the field is getting set forcing the check still throws the same exception, as if it hadn't been set. So perhaps dunni is correct and there is no way for this to work with #Bean notation.
As you said I also could not get #Required to run as expected this may be a bug and needs to be reported. I have a few other suggestions that did work for me.
Class annotated with #Configuration
//With the bean set up as usual These all worked
#Bean
public MyClass myClass() {
return new MyClass();
}
When you annotate the class #Component and load using component scanning works as expected.(The component scanning part is important you either need your #Configuration class to either have #ComponentScan or perhaps remove #Configuration and replace with #SpringBootApplication and this will enable scanning for components without needing to wire them up using #Bean configs)
#Component // Added this
public class MyClass {
...
#Required //Failed as expected
public void setSomeProp(String val){
}
}
Use #Autowired(required=true) //Fails with BeanCreationException //No qualifying bean of type [java.lang.String] found for dependency
//No more #Component
public class MyClass {
...
#Autowired(required=true) //Fails
public void setSomeProp(String val){
}
}
#Autowired required=false //Does not crash
public class MyClass {
...
#Autowired(required=false) //Simply never gets called if missing
public void setSomeProp(String val){
}
}
#Value //Does not work if test.property is missing // Could not resolve placeholder 'test.property' in string value "${test.property}
public class MyClass {
#Value("${test.property}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp;
}
}
#Value with default value//Does not crash // When getSomeProp is called it returns "My Default Value"(Unless you have test.property=Anything in your application.properties file then it returns "Anything"
public class MyClass {
#Value("${test.property:My Default Value}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp; //Returns "My Default Value"
}
}
Inside your #Configuration file also fails if it cannot find anything to populate String someProp in the myClass method
#Bean
public MyClass myClass(String someProp) { //Fails being unable to populate this arg
MyClass myObj = new MyClass();
myObj.setSomeProp(someProp);
return ;
}
If course this won't work, since you create the object of MyClass yourself (new MyClass()), thus the annotations are not evaluated. If you create a bean with a #Bean method, the container will only make sure, that all dependencies are there (method parameters) and that the bean scope is adhered to, meaning if it's a singleton bean, only one bean is created per application context. The creation of the bean/object itself is solely the responsibility of the developer.
The equivalent of the xml <bean> tag is annotating the class with #Component, where the bean is created completely by the container, thus the annotations are evaluated.
As it is being said that when you are having your own #Configuration class where you are creating the bean by itself, #Required doesn't apply there.
When you already have a #Component, let Spring Boot do the component scan and at the required setter property you can add #Autowired and it will work fine.
Found this link on web- https://www.boraji.com/spring-required-annotation-example
For example:
I have a Component called Employee having Id and Name.
#Component
public class Employee {
int id;
String name;
public int getId() {
return id;
}
#Autowired
#Required
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I have a Configuration class called AppConfig.java
#Configuration
public class AppConfig {
#Bean
public int getId() {
return 1;
}
}
So now we see, that component Employee needs an Id property for binding during startup, so I wrote bean method of type Integer, which will get autowired during runtime. If you do not write a bean of type Integer, it will result a BeanCreationException.
And here is my main class file.
#SpringBootApplication
public class SingletonApplication {
public static void main(String[] args) {
ApplicationContext ctx =
SpringApplication.run(SingletonApplication.class, args);
Employee emp = (Employee)ctx.getBean(Employee.class);
System.out.println(emp.getId());
}
}

How do I post-process beans of #Configuration classes that define more #Beans in JavaConfig?

In Spring XML, I can define a bean that instantiates a class annotated with #Configuration. When I do, that bean is post-processed. Any methods inside that class with #Bean are also added to the container. How do I perform a similar post-processing in JavaConfig?
Here's the XML version:
<bean id="test" class="com.so.Test">
<property name="prop" value="set before instantiating #Beans defined in Test"/>
</bean>
The associated Test class:
#Configuration
class Test {
private String prop;
void setProp(final String prop) {
this.prop = prop;
}
#Bean
NeedThisBean needThisBeanToo() {
return new NeedThisBean(prop);
}
}
If I use Spring XML Config, both test and needThisBeanToo are available in the container. needThisBeanToo is added via a BeanPostProcessor, though I can't recall which one. If I use JavaConfig, only test is available in the container. How do I make needThisBeanToo available to the container? #Import would work, except that prop being set is required for needThisBeanToo to be initialized correctly.
The part that makes all of this complicated is that Test is vended from a library I'm consuming. I don't control Test, nor can I change it. If I drive it from JavaConfig, it would look like this:
#Configuration
class MyConfiguration
{
#Bean
Test test() {
Test test = new Test();
test.setProp("needed to init `needThisBeanToo` and others");
return test;
}
}
The JavaConfig example does not instantiate needThisBeanToo despite it being defined in Test. I need to get needThisBeanToo defined, preferably without doing it myself, since I don't want to copy code I don't own. Delegation isn't attractive, since there are a number of subsequent annotations/scopes defined on needThisBeanToo (and others defined inside Test).
Your problem is is that you're ignoring the #Configuration annotation completely. Why is that?
When code reaches this line Test test = new Test(); it just doesn't do anything with #Configuration. Why? Because annotation is not something that a constructor is aware of. Annotation only marks some meta-data for the class. When spring loads classes it searches for annotations, when you call a constructor of a class, you don't. So the #Configuration is just ignored because you instantiate Test with new Test() and not through spring.
What you need to do is to import Test as a spring bean. Either via XML as you showed in your question OR using #Import. You problem with prop is that the setter isn't called because that's just not the way to do it. What you need to be doing is either do something like that:
#Configuration
class Test {
private String prop = "set before instantiating #Beans defined in Test";
#Bean
NeedThisBean needThisBeanToo() {
return new NeedThisBean(prop);
}
}
Or to create a property in spring (this is a different subject) and inject the value:
#Configuration
class Test {
#Autowired
#Value("${some.property.to.inject}") // You can also use SPeL syntax with #{... expression ...}
private String prop;
#Bean
NeedThisBean needThisBeanToo() {
return new NeedThisBean(prop);
}
}
You can also create a bean of type String and inject it as follows:
#Configuration
class Test {
#Autowired
#Qualifer("nameOfBeanToInject")
private String prop;
#Bean
NeedThisBean needThisBeanToo() {
return new NeedThisBean(prop);
}
}
In the last case you can define your original MyConfiguration with this bean:
#Configuration
#Import(Test.class)
class MyConfiguration
{
#Bean(name = "nameOfBeanToInject")
String test() {
return "needed to init `needThisBeanToo` and others";
}
}
In any case you have to import Test either using #Import or as a normal XML bean. It won't work by calling the constructor explicitly.
Here's a way to handle vended #Configuration classes that require some properties to be set prior to creating their #Beans:
Vended #Configuration class:
#Configuration
class Test {
private String property;
public setProperty(final String property) {
this.property = property;
}
#Bean
PropertyUser propertyUser() {
return new PropertyUser(property);
}
#Bean
SomeBean someBean() {
// other instantiation logic
return new SomeBeanImpl();
}
}
Here's the consuming #Configuration class:
#Configuration
class MyConfig {
#Bean
static String myProperty() {
// Create myProperty
}
/**
* Extending Test allows Spring JavaConfig to create
* the beans provided by Test. Declaring
* Test as a #Bean does not provide the #Beans defined
* within it.
*/
#Configuration
static class ModifiedTest extends Test {
ModifiedTest() {
this.setProperty(myProperty());
}
#Override
#Bean
SomeBean someBean() {
return new SomeBeanCustomImpl(this.propertyUser());
}
}

Resources