
Unit Test Config Properties
- 1.6k installs
- 311 repo stars
- Updated June 22, 2026
- giuseppe-trisciuoglio/developer-kit
unit-test-config-properties is an agent skill that provides patterns for unit testing `@configurationproperties` classes with `@configurationpropertiestest`. validates property binding, tests validation constraints, veri
About
unit-test-config-properties is an agent skill from giuseppe-trisciuoglio/developer-kit that provides patterns for unit testing `@configurationproperties` classes with `@configurationpropertiestest`. validates property binding, tests validation constraints, verifies default values, checks typ. # Unit Testing Configuration Properties and Profiles ## Overview This skill provides patterns for unit testing `@ConfigurationProperties` bindings, environment-specific configurations, and property validation using JUnit 5. Covers testing property name mapping, type conversions, validation constraints, nested structures, and profile-specific conf Developers invoke unit-test-config-properties during ship/testing work for testing & qa tasks. The skill documents triggers, prerequisites, and step-by-step workflows grounded in SKILL.md. Compatible with Claude Code, Cursor, and Codex agent runtimes that load marketplace skills. Review the Security Audits panel on this listing before installing in production environments. Category Testing & QA with development vertical focus supports repeatable agent-guided delivery.
- Unit Testing Configuration Properties and Profiles
- Key validation checkpoints:**
- Property prefix matches between `@ConfigurationProperties` and test properties
- Validation triggers on `@Validated` classes with invalid values
- Type conversions work for Duration, DataSize, collections, and maps
Unit Test Config Properties by the numbers
- 1,628 all-time installs (skills.sh)
- +55 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #446 of 2,184 Testing & QA skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
unit-test-config-properties capabilities & compatibility
- Capabilities
- unit testing configuration properties and profil · key validation checkpoints:** · property prefix matches between `@configurationp · validation triggers on `@validated` classes with · type conversions work for duration, datasize, co
- Use cases
- orchestration
What unit-test-config-properties says it does
- Property prefix matches between `@ConfigurationProperties` and test properties
- Validation triggers on `@Validated` classes with invalid values
- Type conversions work for Duration, DataSize, collections, and maps
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-config-propertiesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.6k |
|---|---|
| repo stars | ★ 311 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 22, 2026 |
| Repository | giuseppe-trisciuoglio/developer-kit ↗ |
What it does
Provides patterns for unit testing `@ConfigurationProperties` classes with `@ConfigurationPropertiesTest`. Validates property binding, tests validation constraints, verifies default values, checks typ
Who is it for?
Developers working on testing & qa during ship tasks.
Skip if: Tasks outside Testing & QA scope described in SKILL.md.
When should I use this skill?
Provides patterns for unit testing `@ConfigurationProperties` classes with `@ConfigurationPropertiesTest`. Validates property binding, tests validation constraints, verifies default values, checks typ
What you get
Completed testing & qa workflow aligned with SKILL.md steps.
- JUnit binding test classes
- nested property test coverage
By the numbers
- Demonstrates nested DatabaseProperties with Pool and List<Replica> inner classes
Files
Unit Testing Configuration Properties and Profiles
Overview
This skill provides patterns for unit testing @ConfigurationProperties bindings, environment-specific configurations, and property validation using JUnit 5. Covers testing property name mapping, type conversions, validation constraints, nested structures, and profile-specific configurations without full Spring context startup.
Key validation checkpoints:
- Property prefix matches between
@ConfigurationPropertiesand test properties - Validation triggers on
@Validatedclasses with invalid values - Type conversions work for Duration, DataSize, collections, and maps
When to Use
- Testing
@ConfigurationPropertiesproperty binding - Testing property name mapping and type conversions
- Validating configuration with
@NotBlank,@Min,@Max,@Emailconstraints - Testing environment-specific configurations (dev, prod)
- Testing nested property structures and collections
- Verifying default values when properties are not specified
- Fast configuration tests without Spring context startup
Instructions
1. Set up test dependencies: Add spring-boot-starter-test and AssertJ dependencies 2. Use ApplicationContextRunner: Test property bindings without starting full Spring context 3. Define property prefixes: Ensure @ConfigurationProperties(prefix = "...") matches test property paths 4. Test all property paths: Verify each property including nested structures and collections 5. Test validation constraints: Use context.hasFailed() to verify @Validated properties reject invalid values 6. Test type conversions: Verify Duration (30s), DataSize (50MB), collections, and maps convert correctly 7. Test default values: Verify properties have correct defaults when not specified in test properties 8. Test profile-specific configs: Use @Profile with ApplicationContextRunner for environment-specific configurations 9. Test edge cases: Include empty strings, null values, and type mismatches
Troubleshooting flow:
- If properties don't bind → Check prefix matches (kebab-case to camelCase conversion)
- If validation doesn't trigger → Verify
@Validatedannotation is present - If context fails to start → Check dependencies and
@ConfigurationPropertiesclass structure
Examples
Setup: Test Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>Basic Pattern: Property Binding
@ConfigurationProperties(prefix = "app.security")
@Data
public class SecurityProperties {
private String jwtSecret;
private long jwtExpirationMs;
private int maxLoginAttempts;
private boolean enableTwoFactor;
}
class SecurityPropertiesTest {
@Test
void shouldBindPropertiesFromEnvironment() {
new ApplicationContextRunner()
.withPropertyValues(
"app.security.jwtSecret=my-secret-key",
"app.security.jwtExpirationMs=3600000",
"app.security.maxLoginAttempts=5",
"app.security.enableTwoFactor=true"
)
.withBean(SecurityProperties.class)
.run(context -> {
SecurityProperties props = context.getBean(SecurityProperties.class);
assertThat(props.getJwtSecret()).isEqualTo("my-secret-key");
assertThat(props.getJwtExpirationMs()).isEqualTo(3600000L);
assertThat(props.getMaxLoginAttempts()).isEqualTo(5);
assertThat(props.isEnableTwoFactor()).isTrue();
});
}
@Test
void shouldUseDefaultValuesWhenPropertiesNotProvided() {
new ApplicationContextRunner()
.withPropertyValues("app.security.jwtSecret=key")
.withBean(SecurityProperties.class)
.run(context -> {
SecurityProperties props = context.getBean(SecurityProperties.class);
assertThat(props.getJwtSecret()).isEqualTo("key");
assertThat(props.getMaxLoginAttempts()).isZero();
});
}
}Validation Testing
@ConfigurationProperties(prefix = "app.server")
@Data
@Validated
public class ServerProperties {
@NotBlank
private String host;
@Min(1)
@Max(65535)
private int port = 8080;
@Positive
private int threadPoolSize;
}
class ConfigurationValidationTest {
@Test
void shouldFailValidationWhenHostIsBlank() {
new ApplicationContextRunner()
.withPropertyValues(
"app.server.host=",
"app.server.port=8080",
"app.server.threadPoolSize=10"
)
.withBean(ServerProperties.class)
.run(context -> {
assertThat(context).hasFailed()
.getFailure()
.hasMessageContaining("host");
});
}
@Test
void shouldPassValidationWithValidConfiguration() {
new ApplicationContextRunner()
.withPropertyValues(
"app.server.host=localhost",
"app.server.port=8080",
"app.server.threadPoolSize=10"
)
.withBean(ServerProperties.class)
.run(context -> {
assertThat(context).hasNotFailed();
assertThat(context.getBean(ServerProperties.class).getHost()).isEqualTo("localhost");
});
}
}Type Conversion Testing
@ConfigurationProperties(prefix = "app.features")
@Data
public class FeatureProperties {
private Duration cacheExpiry = Duration.ofMinutes(10);
private DataSize maxUploadSize = DataSize.ofMegabytes(100);
private List<String> enabledFeatures;
private Map<String, String> featureFlags;
}
class TypeConversionTest {
@Test
void shouldConvertDurationFromString() {
new ApplicationContextRunner()
.withPropertyValues("app.features.cacheExpiry=30s")
.withBean(FeatureProperties.class)
.run(context -> {
assertThat(context.getBean(FeatureProperties.class).getCacheExpiry())
.isEqualTo(Duration.ofSeconds(30));
});
}
@Test
void shouldConvertCommaDelimitedList() {
new ApplicationContextRunner()
.withPropertyValues("app.features.enabledFeatures=feature1,feature2")
.withBean(FeatureProperties.class)
.run(context -> {
assertThat(context.getBean(FeatureProperties.class).getEnabledFeatures())
.containsExactly("feature1", "feature2");
});
}
}For nested properties, profile-specific configurations, collection binding, and advanced validation patterns, see references/advanced-examples.md.
Best Practices
- Test all property bindings including nested structures and collections
- Test validation constraints for all
@NotBlank,@Min,@Max,@Email,@Positiveannotations - Test both default and custom values to verify fallback behavior
- Use ApplicationContextRunner for fast context-free testing
- Test profile-specific configurations separately with
@Profile - Verify type conversions for Duration, DataSize, collections, and maps
- Test edge cases: empty strings, null values, type mismatches, out-of-range values
Constraints and Warnings
- Kebab-case to camelCase: Property
app.my-propertymaps tomyPropertyin Java - Loose binding: Spring Boot uses loose binding by default; use strict binding if needed
- `@Validated` required: Add
@Validatedannotation to enable constraint validation - `@ConstructorBinding`: All parameters must be bindable when using constructor binding
- List indexing: Use
[0],[1]notation; ensure sequential indexing for lists - Duration format: Accepts ISO-8601 (
PT30S) or simple syntax (30s,1m,2h) - Context isolation: Each
ApplicationContextRunnercreates a new context with no shared state - Profile activation: Use
spring.profiles.active=profileNameinwithPropertyValues()for profile tests
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Properties not binding | Prefix mismatch | Verify @ConfigurationProperties(prefix="...") matches property paths |
| Validation not triggered | Missing @Validated | Add @Validated annotation to configuration class |
| Context fails to start | Missing dependencies | Ensure spring-boot-starter-test is in test scope |
| Nested properties null | Inner class missing | Use @Data on nested classes or provide getters/setters |
| Collection binding fails | Wrong indexing | Use [0], [1] notation, not (0), (1) |
Advanced ConfigurationProperties Testing Examples
Nested Configuration Properties
Complex Property Structure
@ConfigurationProperties(prefix = "app.database")
@Data
public class DatabaseProperties {
private String url;
private String username;
private Pool pool = new Pool();
private List<Replica> replicas = new ArrayList<>();
@Data
public static class Pool {
private int maxSize = 10;
private int minIdle = 5;
private long connectionTimeout = 30000;
}
@Data
public static class Replica {
private String name;
private String url;
private int priority;
}
}
class NestedPropertiesTest {
@Test
void shouldBindNestedProperties() {
new ApplicationContextRunner()
.withPropertyValues(
"app.database.url=jdbc:mysql://localhost/db",
"app.database.username=admin",
"app.database.pool.maxSize=20",
"app.database.pool.minIdle=10",
"app.database.pool.connectionTimeout=60000"
)
.withBean(DatabaseProperties.class)
.run(context -> {
DatabaseProperties props = context.getBean(DatabaseProperties.class);
assertThat(props.getUrl()).isEqualTo("jdbc:mysql://localhost/db");
assertThat(props.getPool().getMaxSize()).isEqualTo(20);
assertThat(props.getPool().getConnectionTimeout()).isEqualTo(60000L);
});
}
@Test
void shouldBindListOfReplicas() {
new ApplicationContextRunner()
.withPropertyValues(
"app.database.replicas[0].name=replica-1",
"app.database.replicas[0].url=jdbc:mysql://replica1/db",
"app.database.replicas[0].priority=1",
"app.database.replicas[1].name=replica-2",
"app.database.replicas[1].url=jdbc:mysql://replica2/db",
"app.database.replicas[1].priority=2"
)
.withBean(DatabaseProperties.class)
.run(context -> {
DatabaseProperties props = context.getBean(DatabaseProperties.class);
assertThat(props.getReplicas()).hasSize(2);
assertThat(props.getReplicas().get(0).getName()).isEqualTo("replica-1");
assertThat(props.getReplicas().get(1).getPriority()).isEqualTo(2);
});
}
}Profile-Specific Configurations
Environment-Specific Properties
@Configuration
@Profile("prod")
class ProductionConfiguration {
@Bean
public SecurityProperties securityProperties() {
SecurityProperties props = new SecurityProperties();
props.setEnableTwoFactor(true);
props.setMaxLoginAttempts(3);
return props;
}
}
@Configuration
@Profile("dev")
class DevelopmentConfiguration {
@Bean
public SecurityProperties securityProperties() {
SecurityProperties props = new SecurityProperties();
props.setEnableTwoFactor(false);
props.setMaxLoginAttempts(999);
return props;
}
}
class ProfileBasedConfigurationTest {
@Test
void shouldLoadProductionConfiguration() {
new ApplicationContextRunner()
.withPropertyValues("spring.profiles.active=prod")
.withUserConfiguration(ProductionConfiguration.class)
.run(context -> {
SecurityProperties props = context.getBean(SecurityProperties.class);
assertThat(props.isEnableTwoFactor()).isTrue();
assertThat(props.getMaxLoginAttempts()).isEqualTo(3);
});
}
@Test
void shouldLoadDevelopmentConfiguration() {
new ApplicationContextRunner()
.withPropertyValues("spring.profiles.active=dev")
.withUserConfiguration(DevelopmentConfiguration.class)
.run(context -> {
SecurityProperties props = context.getBean(SecurityProperties.class);
assertThat(props.isEnableTwoFactor()).isFalse();
assertThat(props.getMaxLoginAttempts()).isEqualTo(999);
});
}
}Map-Based Properties
@ConfigurationProperties(prefix = "app.feature-flags")
@Data
public class FeatureFlagProperties {
private Map<String, Boolean> flags = new HashMap<>();
private Map<String, FeatureConfig> features = new HashMap<>();
@Data
public static class FeatureConfig {
private boolean enabled;
private String description;
private List<String> allowedUsers;
}
}
class MapPropertiesTest {
@Test
void shouldBindSimpleBooleanMap() {
new ApplicationContextRunner()
.withPropertyValues(
"app.feature-flags.flags.dark-mode=true",
"app.feature-flags.flags.beta-features=false"
)
.withBean(FeatureFlagProperties.class)
.run(context -> {
FeatureFlagProperties props = context.getBean(FeatureFlagProperties.class);
assertThat(props.getFlags()).containsEntry("dark-mode", true);
assertThat(props.getFlags()).containsEntry("beta-features", false);
});
}
@Test
void shouldBindNestedMapStructures() {
new ApplicationContextRunner()
.withPropertyValues(
"app.feature-flags.features.payment.enabled=true",
"app.feature-flags.features.payment.description=Payment module",
"app.feature-flags.features.payment.allowedUsers[0]=admin",
"app.feature-flags.features.payment.allowedUsers[1]=finance"
)
.withBean(FeatureFlagProperties.class)
.run(context -> {
FeatureFlagProperties props = context.getBean(FeatureFlagProperties.class);
FeatureFlagProperties.FeatureConfig payment = props.getFeatures().get("payment");
assertThat(payment.isEnabled()).isTrue();
assertThat(payment.getDescription()).isEqualTo("Payment module");
assertThat(payment.getAllowedUsers()).containsExactly("admin", "finance");
});
}
}Default Values Testing
@ConfigurationProperties(prefix = "app.cache")
@Data
public class CacheProperties {
private long ttlSeconds = 300;
private int maxSize = 1000;
private boolean enabled = true;
private String cacheType = "IN_MEMORY";
}
class DefaultValuesTest {
@Test
void shouldUseDefaultValuesWhenNotSpecified() {
new ApplicationContextRunner()
.withBean(CacheProperties.class)
.run(context -> {
CacheProperties props = context.getBean(CacheProperties.class);
assertThat(props.getTtlSeconds()).isEqualTo(300L);
assertThat(props.getMaxSize()).isEqualTo(1000);
assertThat(props.isEnabled()).isTrue();
assertThat(props.getCacheType()).isEqualTo("IN_MEMORY");
});
}
@Test
void shouldOverrideDefaultValuesWithProvidedProperties() {
new ApplicationContextRunner()
.withPropertyValues(
"app.cache.ttlSeconds=600",
"app.cache.cacheType=REDIS"
)
.withBean(CacheProperties.class)
.run(context -> {
CacheProperties props = context.getBean(CacheProperties.class);
assertThat(props.getTtlSeconds()).isEqualTo(600L);
assertThat(props.getCacheType()).isEqualTo("REDIS");
assertThat(props.getMaxSize()).isEqualTo(1000); // Default unchanged
});
}
}DataSize and Duration Advanced Patterns
@ConfigurationProperties(prefix = "app.upload")
@Data
public class UploadProperties {
private DataSize maxFileSize = DataSize.ofMegabytes(10);
private DataSize maxTotalSize = DataSize.ofGigabytes(1);
private Duration timeout = Duration.ofSeconds(30);
private List<DataSize> allowedExtensions;
}
class DataSizeDurationTest {
@Test
void shouldConvertVariousDurationFormats() {
new ApplicationContextRunner()
.withPropertyValues(
"app.upload.timeout=2h30m",
"app.upload.maxFileSize=25MB",
"app.upload.maxTotalSize=5GB"
)
.withBean(UploadProperties.class)
.run(context -> {
UploadProperties props = context.getBean(UploadProperties.class);
assertThat(props.getTimeout()).isEqualTo(Duration.ofHours(2).plusMinutes(30));
assertThat(props.getMaxFileSize()).isEqualTo(DataSize.ofMegabytes(25));
assertThat(props.getMaxTotalSize()).isEqualTo(DataSize.ofGigabytes(5));
});
}
@Test
void shouldHandleIsoDurationFormat() {
new ApplicationContextRunner()
.withPropertyValues("app.upload.timeout=PT1H30M")
.withBean(UploadProperties.class)
.run(context -> {
assertThat(context.getBean(UploadProperties.class).getTimeout())
.isEqualTo(Duration.ofHours(1).plusMinutes(30));
});
}
}References
Related skills
Forks & variants (1)
Unit Test Config Properties has 1 known copy in the catalog totaling 21 installs. They canonicalize to this original listing.
- giuseppe-trisciuoglio - 21 installs
How it compares
Use unit-test-config-properties for nested binding unit tests; prefer @SpringBootTest integration tests when configuration depends on full application context loading.
FAQ
What does unit-test-config-properties do?
Provides patterns for unit testing `@ConfigurationProperties` classes with `@ConfigurationPropertiesTest`. Validates property binding, tests validation constraints, verifies default values, checks typ
When should I use unit-test-config-properties?
During ship testing work for testing & qa.
Is unit-test-config-properties safe to install?
Review the Security Audits panel on this listing before production use.