Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ jobs:
- name: Test
run: ./mvnw $MAVEN_ARGS -Psnapshots -Djunit-framework.version=${{ matrix.junit-framework }} verify

spring-framework:

name: Spring Framework ${{ matrix.spring-framework }}
strategy:
fail-fast: false
matrix:
spring-framework: [6.2.12, 7.0.0-RC3]
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: '25'
distribution: 'zulu'
cache: maven
- name: Test
run: ./mvnw $MAVEN_ARGS -Dspring-framework.version=${{ matrix.spring-framework }} verify

javadoc:

name: Javadoc
Expand Down
2 changes: 0 additions & 2 deletions docs/converters/base64.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
---
description: An argument converter to decode Base64 instances into byte arrays
hide:
- toc
---

# `@Base64`
Expand Down
2 changes: 0 additions & 2 deletions docs/converters/hex.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
---
description: An argument converter to decode hexadecimal strings into byte arrays
hide:
- toc
---

# `@Hex`
Expand Down
103 changes: 103 additions & 0 deletions docs/converters/spring-conversion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
description: An argument converter using the Spring Framework type conversion
---

# `@SpringConversion`

`@SpringConversion` is an annotation that converts instances using the
[type conversion](https://docs.spring.io/spring-framework/reference/core/validation/convert.html)
provided by the Spring Framework:

``` java
--8<--
SpringConversionDemo.java:import
SpringConversionDemo.java:test
--8<--
```

The converter delegates the conversion to the
[`DefaultConversionService`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/support/DefaultConversionService.html),
which provides a wide range of built-in converters for common Java types, including:

* Primitives and their wrappers
* Arrays
* Collections (`List`, `Set`, `Map`, etc.)
* Enums
* Common value types (`UUID`, `Currency`, `Locale`, etc.)

## Requirements

The annotation requires `spring-core` available in the test classpath:

=== ":simple-apachemaven: Maven"

``` xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
<scope>test</scope>
</dependency>
```

=== ":simple-gradle: Gradle"

``` kotlin
testImplementation("org.springframework:spring-core:${springFrameworkVersion}")
```

However, declaring this dependency is generally not required in a Spring application.

## Examples

The following sections demonstrate some of the possible conversions. For a complete list of supported conversions,
refer to the Spring Framework reference documentation.

### Array → Array

| Source Type | Target Declaration | Example |
|-------------------|----------------------------------|--------------------------------------------------------------------------|
| `#!java String[]` | `#!java @SpringConversion int[]` | `#!java new String[] { "123", "456" }` → `#!java new int[] { 123, 456 }` |


### Array → Collection

| Source Type | Target Declaration | Example |
|-----------------------|------------------------------------------|---------------------------------------------------------------------|
| `#!java int[]` | `#!java @SpringConversion List<Integer>` | `#!java new int[] { 123, 456 }` → `#!java List.of(123, 456)` |
| `#!java Integer[]` | `#!java @SpringConversion List<Integer>` | `#!java new Integer[] { 123, 456 }` → `#!java List.of(123, 456)` |
| `#!java String[]` | `#!java @SpringConversion List<Integer>` | `#!java new String[] { "123", "456" }` → `#!java List.of(123, 456)` |

### Array → Object

| Source Type | Target Declaration | Example |
|--------------------|--------------------------------|-------------------------------------------------------|
| `#!java int[]` | `#!java @SpringConversion int` | `#!java new int[] { 123, 456 }` → `#!java 123` |
| `#!java Integer[]` | `#!java @SpringConversion int` | `#!java new Integer[] { 123, 456 }` → `#!java 123` |
| `#!java String[]` | `#!java @SpringConversion int` | `#!java new String[] { "123", "456" }` → `#!java 123` |

### Array → String

| Source Type | Target Declaration | Example |
|--------------------|-----------------------------------|-------------------------------------------------------------|
| `#!java int[]` | `#!java @SpringConversion String` | `#!java new int[] { 123, 456 }` → `#!java "123,456"` |
| `#!java Integer[]` | `#!java @SpringConversion String` | `#!java new Integer[] { 123, 456 }` → `#!java "123,456"` |
| `#!java String[]` | `#!java @SpringConversion String` | `#!java new String[] { "123", "456" }` → `#!java "123,456"` |

### Collection → Collection

| Source Type | Target Declaration | Example |
|-----------------------|------------------------------------------|-------------------------------------------------------------|
| `#!java List<String>` | `#!java @SpringConversion List<Integer>` | `#!java List.of("123", "456")` → `#!java List.of(123, 456)` |

### Map → Map

| Source Type | Target Declaration | Example |
|------------------------------|-------------------------------------------------|-------------------------------------------------------------------------------|
| `#!java Map<String, String>` | `#!java @SpringConversion Map<Integer, Double>` | `#!java Map.of("1", "123", "2", "456")` → `#!java Map.of(1, 123.0, 2, 456.0)` |

### String → Collection

| Source Type | Target Declaration | Example |
|-----------------------|------------------------------------------|---------------------------------------------------------------------|
| `#!java String` | `#!java @SpringConversion List<Integer>` | `#!java "123, 456"` → `#!java List.of(123, 456)` |
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ The following converters are available:
* [`@Base64`](converters/base64.md): decodes Base64 instances into byte arrays
* [`@Bytes`](converters/bytes.md): converts strings or numbers into byte arrays
* [`@Hex`](converters/hex.md): decodes hexadecimal strings into byte arrays
* [`@SpringConversion`](converters/spring-conversion.md): converts instances using the Spring Framework type conversion

Do you have in mind another converter for your use case?
[:fontawesome-brands-github: Raise an issue!](https://github.com/scordio/junit-converters/issues/new)
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ theme:
features:
- content.code.copy
- navigation.sections
- toc.integrate
icon:
repo: fontawesome/brands/github
favicon: assets/images/logo.png
Expand Down Expand Up @@ -91,5 +92,6 @@ nav:
- converters/base64.md
- converters/bytes.md
- converters/hex.md
- converters/spring-conversion.md
- javadoc.md
- release-notes.md
33 changes: 33 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Dependency versions -->
<junit-framework.version>5.14.0</junit-framework.version>
<spring-framework.version>5.3.39</spring-framework.version>
</properties>

<dependencyManagement>
Expand All @@ -62,6 +63,13 @@
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Optional -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
<optional>true</optional>
</dependency>
<!-- Provided -->
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down Expand Up @@ -231,6 +239,31 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.2</version>
<configuration>
<rules>
<bannedDependencies>
<includes>
<include>org.jspecify:jspecify</include>
</includes>
<excludes>
<exclude>*:*:*:jar:compile</exclude>
</excludes>
</bannedDependencies>
<dependencyConvergence/>
</rules>
</configuration>
<executions>
<execution>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
Expand Down
41 changes: 41 additions & 0 deletions src/demo/java/io/github/scordio/SpringConversionDemo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright © 2025 Stefano Cordio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.scordio;

// --8<-- [start:import]
import io.github.scordio.junit.converters.SpringConversion;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
// --8<-- [end:import]

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

class SpringConversionDemo {

// @formatter:off
// --8<-- [start:test]

@ParameterizedTest
@ValueSource(strings = "123, 456")
void test(@SpringConversion List<Integer> ints) {
assertEquals(List.of(123, 456), ints);
}
// --8<-- [end:test]
// @formatter:on

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright © 2025 Stefano Cordio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.scordio.junit.converters;

import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.converter.ArgumentConversionException;
import org.junit.jupiter.params.converter.ArgumentConverter;
import org.junit.jupiter.params.support.FieldContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;

class SpringArgumentConverter implements ArgumentConverter {

private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();

@Override
public @Nullable Object convert(@Nullable Object source, ParameterContext context)
throws ArgumentConversionException {
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forParameter(context.getParameter()));
return CONVERSION_SERVICE.convert(source, sourceType, targetType);
}

@Override
public @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException {
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
TypeDescriptor targetType = new TypeDescriptor(context.getField());
return CONVERSION_SERVICE.convert(source, sourceType, targetType);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright © 2025 Stefano Cordio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.scordio.junit.converters;

import org.junit.jupiter.params.converter.ConvertWith;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* {@link ConvertWith} composed annotation that converts arguments using the Spring
* {@link org.springframework.core.convert.ConversionService conversion service}.
*
* @see org.springframework.core.convert.ConversionService
* @see org.springframework.core.convert.support.DefaultConversionService
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConvertWith(SpringArgumentConverter.class)
@SuppressWarnings("exports")
public @interface SpringConversion {

}
3 changes: 3 additions & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@
* @see org.junit.jupiter.params.ParameterizedClass
* @see org.junit.jupiter.params.ParameterizedTest
*/
@SuppressWarnings("requires-automatic")
module io.github.scordio.junit.converters {

requires static transitive org.jspecify;

requires static spring.core;

requires org.junit.jupiter.params;

exports io.github.scordio.junit.converters;
Expand Down
Loading
Loading