how to test constructorBinding annotation simply? - spring

I have some properties bean files with ConstructorBinding annotation.
However, it is difficult to do test except for adding the SpringBootTest annotation that takes much of the time.
here is the properties bean file.
#Getter
#ConfigurationProperties(prefix = "sample.datasource")
public class SampleProperties {
private final String url;
private final String username;
private final String password;
#ConstructorBinding
public SampleProperties(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
}
}
I tried adding a class into the SpringBootTest classes field or ConfigurationContext annotation too but it printed the error below.
how can I do test those files simply?
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1801)
at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1357)
at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311)
at app//org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
at app//org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 105 more
the test code is here.
#SpringBootTest(classes = {DatasourcePropertiesConfiguration.class, SampleProperties.class})
class PropertiesConfigurationTest {
#Autowired
private DatasourcePropertiesConfiguration datasourcePropertiesConfiguration;
#Autowired
private SampleProperties sampleProperties;
private static final String url = "localhost:3306";
private static final String username = "root";
private static final String password = "password123^";
#Test
void encoding() {
String encodedUrl = datasourcePropertiesConfiguration.getUrl();
String encodedUsername = datasourcePropertiesConfiguration.getUsername();
String encodedPassword = datasourcePropertiesConfiguration.getPassword();
assertThat(encodedUrl).isEqualTo(url);
assertThat(encodedUsername).isEqualTo(username);
assertThat(encodedPassword).isEqualTo(password);
}
}

Related

Issue with integration test for spring boot with couchdb

I'm pretty new to couchbase but fair experienced with spring boot. I have the following:
Repository:
import entity.Building;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
#Collection("buildings")
public interface BuildingRepository extends CouchbaseRepository<Building, String> {
}
Building:
#Document
public class Building {
#Id
#GeneratedValue(strategy = GenerationStrategy.UNIQUE)
private String id;
#Field
#NotNull
private String erf;
#Field
#NotNull
private String name;
#Field
#NotNull
private Address address;
#CreatedBy
private String creator;
#LastModifiedBy
private String lastModifiedBy;
#LastModifiedDate
private LocalDateTime lastModification;
#CreatedDate
private LocalDateTime creationDate;
#Version
private long version;
}
Test case:
#SpringBootTest
#Testcontainers
#ActiveProfiles({"test"})
#AutoConfigureMockMvc
public class RetrieveBuildingTest {
#Container
private static final CouchbaseContainer COUCHBASE = new CouchbaseContainer(
DockerImageName.parse("couchbase/server:7.1.2")).withBucket(new BucketDefinition("buildings").withPrimaryIndex(true));
#Autowired
private MockMvc mvc;
#Test
public void testLifecycleForBuildings() throws Exception {
final ClassPathResource resource = new ClassPathResource("payloads/successful-create-building.json");
final String payload = Files.readString(Path.of(resource.getURI()));
// save
mvc.perform(post("/building")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(payload))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is("Knightsbridge Mansion")));
// retrieval all
mvc.perform(get("/building"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements", is(1)));
// retrieve specific building by id
}
#DynamicPropertySource
public static void register(final DynamicPropertyRegistry register) {
register.add("spring.couchbase.connection-string", COUCHBASE::getConnectionString);
register.add("spring.couchbase.username", COUCHBASE::getUsername);
register.add("spring.couchbase.password", COUCHBASE::getPassword);
}
I see in some examples there is AbstractCouchbaseConfiguration but I'd think that the properties would have set this up. I've also set the following properties in application.properties:
spring.data.couchbase.bucket-name=buildings
spring.couchbase.env.timeouts.connect=30
spring.couchbase.env.timeouts.key-value-durable=30
Errors from the log:
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:581)
Caused by: com.couchbase.client.core.error.AmbiguousTimeoutException: InsertRequest, Reason: TIMEOUT {"cancelled":true,"completed":true,"coreId":"0xf0d3d25700000001","idempotent":false,"lastChannelId":"F0D3D25700000001/0000000058CD068A","lastDispatchedFrom":"127.0.0.1:38858","lastDispatchedTo":"localhost:49158","reason":"TIMEOUT","requestId":5,"requestType":"InsertRequest","retried":14,"retryReasons":["COLLECTION_MAP_REFRESH_IN_PROGRESS"],"service":{"bucket":"buildings","collection":"buildings","documentId":"54b991d2-fa8b-41eb-9b1e-d5519914e964","opaque":"0x15","scope":"_default","type":"kv","vbucket":233},"timeoutMs":2500,"timings":{"encodingMicros":5785,"totalMicros":2512314}}
at com.couchbase.client.core.msg.BaseRequest.cancel(BaseRequest.java:184)
at com.couchbase.client.core.msg.Request.cancel(Request.java:70)
at com.couchbase.client.core.Timer.lambda$register$2(Timer.java:157)
spring.couchbase.env.timeouts.connect and spring.couchbase.env.timeouts.key-value-durable are missing the unit. Default values are 10 seconds. If not specified then it will convert to milis and that's how the timeout is produced. In order to fix it set 30s.
See more here

hibernate mapping and joining table error

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like #JoinTable or #JoinColumn: com.company.entities.Customer.address
#Entity
#Table(name="Customers")
public class Customer {
#Id
#Column(name="customer_Id")
private int customerid;
#Column(name="Customer_Name")
private String name;
#Column (name="Customer_email")
private String email;
#Column(name="Mobile_No")
private int mobilNo;
#OneToMany(cascade= CascadeType.ALL,mappedBy="customer")
#JoinTable(name="Customer",joinColumns={
#JoinColumn(name="customer_Id", referencedColumnName="customerid")
},
inverseJoinColumns={
#JoinColumn(name="address_Id",referencedColumnName="addId")
})
private Map<String,Address> address=new HashMap<>();
#Entity
public class Address {
#Id
private int addId;
private String StreetName;
private String city;
private String state;
#ManyToOne(cascade=CascadeType.ALL)
private Customer customer;
}

Reading property values in constructor using prefix

How to use the prefix in the below code?
Properties:
height.customer.feet=10
height.customer.eu.timezone=UTC
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "height.customer")
public class Customer {
private final int age;
private final String timezone;
public Customer(int age, String timezone){
this.age = age;
this.timezone = timezone;
}
}
Here i want to set the default value for both age and timezone. Default values are read from application.properties file. Can someone help me please?
I could use like below.
#Value("${height.customer.age}")
private final int age;
#Value("${height.customer.eu.timezone}")
private final String timezone;
But if i use like this, i may not able to use constructor injection
There is no relationship between the #ConfigurationProperties and #Value annotation. Check here. What you should be using is the #PropertySource annotation. If you use #ConfigurationProperties then you should have hierarchical properties
#Configuration
#ConfigurationProperties(prefix = "height.customer")
public class Customer {
private final int age; // This maps to height.customer.age
private final String timezone; // This does NOT map to height.customer.eu.timezone but maps to height.customer.timezone
public Customer(int age, String timezone){
this.age = age;
this.timezone = timezone;
}
}
Use the #PropertySource with this example
#Configuration
#PropertySource("classpath: demo.properties") // your properties file
public class Customer {
#Value("${height.customer.age}")
private final int age;
#Value("${height.customer.eu.timezone}")
private final String timezone;
public Customer(int age, String timezone){
this.age = age;
this.timezone = timezone;
}
public Customer(){}
}
And there won't be a conflict with the existing constructor because, values injecting via #PropertySource will be default values. If you provide values in the constructor, these will get overridden.

SpringBoot check if injected Properties are set NotNull

I know I can easily inject a property file in SpringBoot 2.2 with the following construct
#ConstructorBinding
#ConfigurationProperties(prefix = "example")
#Data
#AllArgsConstructor
public final class MyProps {
#NonNull
private final String neededProperty;
#NonNull
private final List<SampleProps> lstNeededProperty;
public String getFirstSample(){
return lstNeededProperty.get(0); //throws NPE
}
}
#ConstructorBinding
#AllArgsConstructor
#Data
public class SampleProps {
String key;
String label;
}
and yml file like:
expample:
neededProperty: test1
lstNeededProperty:
-key: abc
label: input
The #NonNull works quite well for the String but fails for the List - since the NPE is thrown even when the list will be set.
Is there a simple way to check if the List is initialized? I've tried #Postconstruct but this isn't called at all.
Try to check for the size and intialize the list:
#ConstructorBinding
#ConfigurationProperties(prefix = "example")
public final class MyProps {
#NonNull
private final String neededProperty;
#Size(min=1)
private final List<String> lstNeededProperty = new ArrayList<>();
}

Spring JPA - Ignore a field only in persistence

I have a field called password which can be received by endpoint. But it cannot be sent back in response or persisted in Database
The class is as follows -
public class ShortURL {
#Pattern(regexp="^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]")
private String url;
#Size(min=8,max=16)
#Transient
private String password = null;
private boolean isPasswordProtected = false;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isPasswordProtected() {
return isPasswordProtected;
}
public void setPasswordProtected(boolean isPasswordProtected) {
this.isPasswordProtected = isPasswordProtected;
}
public ShortURL(
#Pattern(regexp = "^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]") String url,
#Size(min = 8, max = 16) String password, boolean isPasswordProtected) {
super();
this.url = url;
this.password = password;
this.isPasswordProtected = isPasswordProtected;
}
#Transient works properly. But adding the #JsonIgnore after #Transient causes problems -
Type definition error: [simple type, class java.lang.String];
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
No fallback setter/field defined for creator property 'password'"
How do I achieve my intentions?
Depends on your Jackson version.
Before version 1.9, you could add #JsonIgnore to the getter of password and add #JsonProperty to the setter of the password field.
Recent versions of Jackson provide READ_ONLY and WRITE_ONLY annotation arguments for #JsonProperty, something like this:
#JsonProperty(access = Access.READ_ONLY)
private String password;
Yes you can use #JsonIgnore to let jackson ignore it during sending the user response but. There are certain best practices you should follow.
Never expose the Entities directly to the endpoint instead its better to have a wrapper i.e DTO that translates your entity to the required response.
For eg. in your case
public class ShortURL {
#Pattern(regexp="^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]")
private String url;
#Size(min=8,max=16)
private String password;
private boolean isPasswordProtected;
}
//here is the dto in which you can create a parameterised constructor and
accordingly invoke it based on the fields you want to set.
public class ShortURLDTO {
private String url;
public ShortURLDTO(String url){
this.url=url
}
}

Resources