unit-test-config-properties

Unit Testing Configuration Properties and Profiles

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "unit-test-config-properties" with this command: npx skills add giuseppe-trisciuoglio/developer-kit/giuseppe-trisciuoglio-developer-kit-unit-test-config-properties

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 @ConfigurationProperties and test properties

  • Validation triggers on @Validated classes with invalid values

  • Type conversions work for Duration, DataSize, collections, and maps

When to Use

  • Testing @ConfigurationProperties property binding

  • Testing property name mapping and type conversions

  • Validating configuration with @NotBlank , @Min , @Max , @Email constraints

  • 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

  • Set up test dependencies: Add spring-boot-starter-test and AssertJ dependencies

  • Use ApplicationContextRunner: Test property bindings without starting full Spring context

  • Define property prefixes: Ensure @ConfigurationProperties(prefix = "...") matches test property paths

  • Test all property paths: Verify each property including nested structures and collections

  • Test validation constraints: Use context.hasFailed() to verify @Validated properties reject invalid values

  • Test type conversions: Verify Duration (30s ), DataSize (50MB ), collections, and maps convert correctly

  • Test default values: Verify properties have correct defaults when not specified in test properties

  • Test profile-specific configs: Use @Profile with ApplicationContextRunner for environment-specific configurations

  • 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 @Validated annotation is present

  • If context fails to start → Check dependencies and @ConfigurationProperties class 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 , @Positive annotations

  • 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-property maps to myProperty in Java

  • Loose binding: Spring Boot uses loose binding by default; use strict binding if needed

  • @Validated required: Add @Validated annotation 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 ApplicationContextRunner creates a new context with no shared state

  • Profile activation: Use spring.profiles.active=profileName in withPropertyValues() 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)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

shadcn-ui

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

tailwind-css-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

unit-test-bean-validation

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-patterns

No summary provided by upstream source.

Repository SourceNeeds Review