Skip to main content
skill CX Codex

unit-test-config-properties — Claude Code skill: how to install

Provides JUnit 5 patterns to unit test Spring Boot @ConfigurationProperties using ApplicationContextRunner without starting a full application context.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-config-properties -a claude-code
Open on GitHub

What unit-test-config-properties does

This skill provides test setups for property binding, validation constraints, and type conversions in Spring Boot. It guides the agent in using ApplicationContextRunner to test prefix binding between kebab-case properties and camelCase Java fields.

It handles validation tests with @Validated, @NotBlank, and @Min via context.hasFailed() checks. The pattern requires spring-boot-starter-test and assertj-core in your build configuration, so tests will not compile without them.

The patterns also test default values, environment profiles, and conversions for types such as Duration or DataSize. Tests execute quickly because no heavy Web context starts up.

What it helps with

  • test @ConfigurationProperties binding without starting the full Spring context
  • verify @Validated constraints such as @NotBlank and @Min on configuration beans
  • check type conversions for Duration, DataSize, maps, and lists
  • verify default property values when configuration keys are omitted

Example prompts for the agent

  • Write an ApplicationContextRunner test for SecurityProperties to verify JWT property binding
  • Generate a unit test for ServerProperties checking that blank host fails validation
  • Test type conversion from string to Duration in FeatureProperties using JUnit 5

How to install unit-test-config-properties

1Skills CLI (Vercel)

npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-config-properties -a claude-code

2Manually: put the files in place

~/.claude/skills/unit-test-config-properties/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/unit-test-config-properties/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about unit-test-config-properties

How to install unit-test-config-properties in Claude Code?

Run npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-config-properties -a claude-code in your terminal.

What does unit-test-config-properties do?

It provides testing patterns for Spring Boot configuration properties, covering property binding, type mapping, and validation.

Does this require starting the full Spring application context?

No, tests rely on ApplicationContextRunner, which creates a minimal context specifically for the target property class.

Source text

SKILL.md · MIT
---
name: unit-test-config-properties
description: Provides patterns for unit testing `@ConfigurationProperties` classes with `@ConfigurationPropertiesTest`. Validates property binding, tests validation constraints, verifies default values, checks type conversions, and mocks property sources for Spring Boot configuration properties. Use when testing application configuration binding, validating YAML or application.properties files, verifying environment-specific settings, or testing nested property structures.
allowed-tools: Read, Write, Bash, Glob, Grep
---

# 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

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 `@Validated` annotation is present
- If context fails to start → Check dependencies and `@ConfigurationProperties` class structure

## Examples

### Setup: Test Dependencies

```xml
<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

```java
@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

```java
@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

```java
@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)` |

© giuseppe-trisciuoglio · MIT · giuseppe-trisciuoglio/developer-kit

Similar skills

All skills for Testing and QA →

From the same set96

aws-sdk-java-v2-s3 Hands your agent ready-made Amazon S3 patterns for AWS SDK for Java 2.x: buckets, object uploads, multipart, presigned URLs. CX Codex +1 3,700installs langchain4j-mcp-server-patterns Provides design patterns for building Java MCP servers and connecting them to LangChain4j. Useful when exposing tools in Spring Boot and securing agent execution. CX Codex +1 3,700installs langchain4j-vector-stores-configuration A skill that shows how to wire a LangChain4J vector store for RAG: connections, embedding dimensions, metadata filters. CX Codex +1 3,700installs unit-test-application-events Unit test patterns for Spring events: verify publishing through ApplicationEventPublisher and exercise @EventListener handlers without booting the context. CX Codex +1 3,700installs unit-test-caching Unit test patterns for Spring Cache annotations: cache hits and misses, eviction, SpEL keys, all without booting a full Spring context. CX Codex +1 3,700installs langchain4j-ai-services-patterns A skill that walks your agent through declarative AI Services in LangChain4j: interfaces, annotations, memory and tool calling. CX Codex +1 3,700installs qdrant Provides integration patterns for the Qdrant vector database in Java and Spring Boot with LangChain4j. Helps build semantic search and vector stores for RAG. CX Codex +1 3,700installs unit-test-mapper-converter A skill for unit-testing MapStruct mappers and converters: field mapping, null handling, nested objects and round-trip conversion. CX Codex +1 3,700installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor