Skip to content

Commit 735d80d

Browse files
committed
Code samples for Validate Spring Boot Configuration Parameters at Startup
1 parent 3eb97d8 commit 735d80d

File tree

9 files changed

+289
-0
lines changed

9 files changed

+289
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.reflectoring.validation;
2+
3+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
7+
@Configuration
8+
@EnableConfigurationProperties(AppProperties.class)
9+
class AppConfiguration {
10+
11+
@Bean
12+
public static ReportEmailAddressValidator configurationPropertiesValidator() {
13+
return new ReportEmailAddressValidator();
14+
}
15+
16+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.reflectoring.validation;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
import org.springframework.validation.annotation.Validated;
5+
6+
import javax.validation.Valid;
7+
import javax.validation.constraints.NotEmpty;
8+
9+
@Validated
10+
@ConfigurationProperties(prefix = "app.properties")
11+
class AppProperties {
12+
13+
@NotEmpty
14+
private String name;
15+
16+
@Valid
17+
private ReportProperties report;
18+
19+
public String getName() {
20+
return name;
21+
}
22+
23+
public void setName(String name) {
24+
this.name = name;
25+
}
26+
27+
public ReportProperties getReport() {
28+
return report;
29+
}
30+
31+
public void setReport(ReportProperties report) {
32+
this.report = report;
33+
}
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.reflectoring.validation;
2+
3+
import org.springframework.validation.Errors;
4+
import org.springframework.validation.ValidationUtils;
5+
import org.springframework.validation.Validator;
6+
7+
class ReportEmailAddressValidator implements Validator {
8+
9+
private static final String EMAIL_DOMAIN = "@analysisapp.com";
10+
11+
public boolean supports(Class clazz) {
12+
return ReportProperties.class.isAssignableFrom(clazz);
13+
}
14+
15+
public void validate(Object target, Errors errors) {
16+
17+
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "emailAddress", "field.required");
18+
19+
ReportProperties reportProperties = (ReportProperties) target;
20+
if (!reportProperties.getEmailAddress().endsWith(EMAIL_DOMAIN)) {
21+
errors.rejectValue("emailAddress", "field.domain.required",
22+
new Object[]{EMAIL_DOMAIN},
23+
"The email address must contain [" + EMAIL_DOMAIN + "] domain");
24+
}
25+
26+
}
27+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.reflectoring.validation;
2+
3+
import javax.validation.constraints.Email;
4+
import javax.validation.constraints.Max;
5+
import javax.validation.constraints.Min;
6+
7+
class ReportProperties {
8+
9+
private Boolean sendEmails = Boolean.FALSE;
10+
11+
private ReportType type = ReportType.HTML;
12+
13+
@Min(value = 7)
14+
@Max(value = 30)
15+
private Integer intervalInDays;
16+
17+
@Email
18+
private String emailAddress;
19+
20+
public Boolean getSendEmails() {
21+
return sendEmails;
22+
}
23+
24+
public void setSendEmails(Boolean sendEmails) {
25+
this.sendEmails = sendEmails;
26+
}
27+
28+
public ReportType getType() {
29+
return type;
30+
}
31+
32+
public void setType(ReportType type) {
33+
this.type = type;
34+
}
35+
36+
public Integer getIntervalInDays() {
37+
return intervalInDays;
38+
}
39+
40+
public void setIntervalInDays(Integer intervalInDays) {
41+
this.intervalInDays = intervalInDays;
42+
}
43+
44+
public String getEmailAddress() {
45+
return emailAddress;
46+
}
47+
48+
public void setEmailAddress(String emailAddress) {
49+
this.emailAddress = emailAddress;
50+
}
51+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.reflectoring.validation;
2+
3+
enum ReportType {
4+
HTML, PLAIN_TEXT
5+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.reflectoring.validation;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.context.annotation.PropertySource;
6+
7+
@SpringBootApplication
8+
@PropertySource("classpath:application-validation.properties")
9+
public class ValidationApplication {
10+
11+
public static void main(String[] args) {
12+
SpringApplication.run(ValidationApplication.class, args);
13+
}
14+
15+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
app.properties.name=Analysis Application
2+
app.properties.report.send-emails=true
3+
app.properties.report.type=PLAIN_TEXT
4+
app.properties.report.interval-in-days=14
5+
app.properties.report.email-address[email protected]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package io.reflectoring.validation;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
5+
import org.springframework.boot.SpringApplication;
6+
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;
7+
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
8+
import org.springframework.core.env.ConfigurableEnvironment;
9+
import org.springframework.core.env.MutablePropertySources;
10+
import org.springframework.core.env.PropertiesPropertySource;
11+
import org.springframework.core.env.StandardEnvironment;
12+
13+
import java.util.Properties;
14+
15+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
16+
17+
/**
18+
* We create Spring Application dynamically to catch and test application context startup exceptions
19+
*/
20+
class AppPropertiesInvalidInputTest {
21+
22+
SpringApplication application;
23+
Properties properties;
24+
25+
@BeforeEach
26+
void setup() {
27+
// create Spring Application dynamically
28+
application = new SpringApplication(ValidationApplication.class);
29+
30+
// setting test properties for our Spring Application
31+
properties = new Properties();
32+
33+
ConfigurableEnvironment environment = new StandardEnvironment();
34+
MutablePropertySources propertySources = environment.getPropertySources();
35+
propertySources.addFirst(new PropertiesPropertySource("application-test", properties));
36+
application.setEnvironment(environment);
37+
}
38+
39+
@Test
40+
void whenGivenNameEmpty_thenNotEmptyValidationFails() {
41+
42+
properties.put("app.properties.name", "");
43+
44+
assertThatThrownBy(application::run)
45+
.isInstanceOf(ConfigurationPropertiesBindException.class)
46+
.hasRootCauseInstanceOf(BindValidationException.class)
47+
.hasStackTraceContaining("Field error in object 'app.properties' on field 'name'")
48+
.hasStackTraceContaining("[must not be empty]");
49+
50+
}
51+
52+
@Test
53+
void whenGivenReportIntervalInDaysMoreThan30_thenMaxValidationFails() {
54+
55+
properties.put("app.properties.report.interval-in-days", "31");
56+
57+
assertThatThrownBy(application::run)
58+
.isInstanceOf(ConfigurationPropertiesBindException.class)
59+
.hasRootCauseInstanceOf(BindValidationException.class)
60+
.hasStackTraceContaining("Field error in object 'app.properties' on field 'report.intervalInDays'")
61+
.hasStackTraceContaining("[must be less than or equal to 30]");
62+
63+
}
64+
65+
@Test
66+
void whenGivenReportIntervalInDaysLessThan7_thenMinValidationFails() {
67+
68+
properties.put("app.properties.report.interval-in-days", "6");
69+
70+
assertThatThrownBy(application::run)
71+
.isInstanceOf(ConfigurationPropertiesBindException.class)
72+
.hasRootCauseInstanceOf(BindValidationException.class)
73+
.hasStackTraceContaining("Field error in object 'app.properties' on field 'report.intervalInDays'")
74+
.hasStackTraceContaining("[must be greater than or equal to 7]");
75+
76+
}
77+
78+
@Test
79+
void whenGivenReportEmailAddressIsNotWellFormed_thenEmailValidationFails() {
80+
81+
properties.put("app.properties.report.email-address", "manager.analysisapp.com");
82+
83+
assertThatThrownBy(application::run)
84+
.isInstanceOf(ConfigurationPropertiesBindException.class)
85+
.hasRootCauseInstanceOf(BindValidationException.class)
86+
.hasStackTraceContaining("Field error in object 'app.properties' on field 'report.emailAddress'")
87+
.hasStackTraceContaining("[must be a well-formed email address]");
88+
89+
}
90+
91+
@Test
92+
void whenGivenReportEmailAddressDoesNotContainAnalysisappDomain_thenCustomEmailValidatorFails() {
93+
94+
properties.put("app.properties.report.email-address", "[email protected]");
95+
96+
assertThatThrownBy(application::run)
97+
.isInstanceOf(ConfigurationPropertiesBindException.class)
98+
.hasRootCauseInstanceOf(BindValidationException.class)
99+
.hasStackTraceContaining("Field error in object 'app.properties.report' on field 'emailAddress'")
100+
.hasStackTraceContaining("[The email address must contain [@analysisapp.com] domain]");
101+
102+
}
103+
104+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.reflectoring.validation;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.springframework.beans.factory.annotation.Autowired;
5+
import org.springframework.boot.test.context.SpringBootTest;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
@SpringBootTest(properties = {
10+
"app.properties.name=My Test App",
11+
"app.properties.report.send-emails=true",
12+
"app.properties.report.type=PLAIN_TEXT",
13+
"app.properties.report.interval-in-days=14",
14+
15+
}, classes = {AppConfiguration.class, AppProperties.class, ReportProperties.class})
16+
class AppPropertiesValidInputTest {
17+
18+
@Autowired
19+
AppProperties appProperties;
20+
21+
@Test
22+
void propertiesAreLoaded() {
23+
assertThat(appProperties).isNotNull();
24+
assertThat(appProperties.getName()).isEqualTo("My Test App");
25+
assertThat(appProperties.getReport()).isNotNull();
26+
assertThat(appProperties.getReport().getSendEmails()).isTrue();
27+
assertThat(appProperties.getReport().getType()).isEqualTo(ReportType.PLAIN_TEXT);
28+
assertThat(appProperties.getReport().getIntervalInDays()).isEqualTo(14);
29+
assertThat(appProperties.getReport().getEmailAddress()).isEqualTo("[email protected]");
30+
}
31+
32+
}

0 commit comments

Comments
 (0)