-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c7bb969
commit 2f4feb7
Showing
8 changed files
with
434 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
:page-title: Simple Arguments Aggregator | ||
:page-description: The JUnit 5 (Jupiter) extension `@Aggregate` aggregates supplied values into a single parameter for a `@ParameterizedTest` | ||
:xp-demo-dir: ../src/demo/java | ||
:demo: {xp-demo-dir}/org/junitpioneer/jupiter/params/SimpleAggregatorDemo.java | ||
|
||
Annotating a test parameter with `@Aggregate` aggregates all the supplied arguments into a single object. | ||
|
||
== Usage | ||
|
||
`@Aggregate` can be applied to a parameter in a `@ParameterizedTest`. | ||
|
||
[source,java,indent=0] | ||
==== | ||
include::{demo}[tag=basic_example] | ||
==== | ||
|
||
== Limitations | ||
|
||
The extension is meant to be used for simple use cases and has a couple of limitations. | ||
|
||
- The parameter object must have a `public` constructor. | ||
- The arguments must be in the same order as the constructor parameters. | ||
- The parameter object must be non-composite - it can not have another object(s) as fields. | ||
|
||
This last point has a few exceptions based on JUnit 5 support for https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-argument-conversion-implicit[implicit type conversions]. | ||
In the example above, if we have the following fields in the `Person` class: | ||
|
||
[source,java,indent=0] | ||
==== | ||
include::{demo}[tag=person_class] | ||
==== | ||
|
||
Then JUnit 5 will take care of the conversion from `String` to `Gender` and `LocalDate`. | ||
If you need to supply more complex objects to your tests, see if link:/docs/json-argument-source.adoc[JSON arguments sources] cover your use case. | ||
|
||
== Thread-Safety | ||
|
||
This extension is safe to use during https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution[parallel test execution]. |
49 changes: 49 additions & 0 deletions
49
src/demo/java/org/junitpioneer/jupiter/params/SimpleAggregatorDemo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright 2016-2022 the original author or authors. | ||
* | ||
* All rights reserved. This program and the accompanying materials are | ||
* made available under the terms of the Eclipse Public License v2.0 which | ||
* accompanies this distribution and is available at | ||
* | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
*/ | ||
|
||
package org.junitpioneer.jupiter.params; | ||
|
||
import java.time.LocalDate; | ||
|
||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.CsvSource; | ||
|
||
public class SimpleAggregatorDemo { | ||
|
||
// tag::basic_example[] | ||
@ParameterizedTest | ||
@CsvSource({ "Jane, Doe, F, 1990-05-20", "John, Doe, M, 1990-10-22" }) | ||
void test(@Aggregate Person person) { | ||
} | ||
// end::basic_example[] | ||
|
||
static class Person { | ||
|
||
// tag::person_class[] | ||
private final String firstName; | ||
private final String lastName; | ||
private final Gender gender; | ||
private final LocalDate birthday; | ||
// end::person_class[] | ||
|
||
public Person(String firstName, String lastName, Gender gender, LocalDate birthday) { | ||
this.firstName = firstName; | ||
this.lastName = lastName; | ||
this.gender = gender; | ||
this.birthday = birthday; | ||
} | ||
|
||
} | ||
|
||
enum Gender { | ||
F, M, X | ||
} | ||
|
||
} |
43 changes: 43 additions & 0 deletions
43
src/main/java/org/junitpioneer/jupiter/params/Aggregate.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright 2016-2022 the original author or authors. | ||
* | ||
* All rights reserved. This program and the accompanying materials are | ||
* made available under the terms of the Eclipse Public License v2.0 which | ||
* accompanies this distribution and is available at | ||
* | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
*/ | ||
|
||
package org.junitpioneer.jupiter.params; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
import org.junit.jupiter.params.aggregator.AggregateWith; | ||
|
||
/** | ||
* {@code @Aggregate} is a parameter annotation, used for simple argument aggregation. | ||
* | ||
* <p>The supplied values are expected to be able to be aggregated into a single argument, | ||
* which is in turn supplied to the {@code @ParameterizedTest} method.</p> | ||
* | ||
* <p>For more details (including its limitations) and examples, see | ||
* <a href="https://junit-pioneer.org/docs/simple-arguments-aggregator/" target="_top">the documentation on | ||
* Simple Arguments Aggregator</a> | ||
* </p> | ||
* | ||
* <p>This annotation is not compatible with {@link org.junitpioneer.jupiter.cartesian.CartesianTest} since | ||
* this expects a single parameter as opposed to {@link org.junitpioneer.jupiter.cartesian.CartesianTest} | ||
* requiring multiple parameters. | ||
* </p> | ||
* | ||
* @since 2.1 | ||
* @see org.junit.jupiter.params.aggregator.ArgumentsAggregator | ||
*/ | ||
@Target(ElementType.PARAMETER) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@AggregateWith(SimpleAggregator.class) | ||
public @interface Aggregate { | ||
} |
79 changes: 79 additions & 0 deletions
79
src/main/java/org/junitpioneer/jupiter/params/SimpleAggregator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* Copyright 2016-2022 the original author or authors. | ||
* | ||
* All rights reserved. This program and the accompanying materials are | ||
* made available under the terms of the Eclipse Public License v2.0 which | ||
* accompanies this distribution and is available at | ||
* | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
*/ | ||
|
||
package org.junitpioneer.jupiter.params; | ||
|
||
import static java.lang.String.format; | ||
import static java.util.stream.Collectors.toUnmodifiableSet; | ||
|
||
import java.lang.reflect.Constructor; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
||
import org.junit.jupiter.api.extension.ParameterContext; | ||
import org.junit.jupiter.params.aggregator.ArgumentsAccessor; | ||
import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; | ||
import org.junit.jupiter.params.aggregator.ArgumentsAggregator; | ||
import org.junitpioneer.internal.PioneerUtils; | ||
|
||
class SimpleAggregator implements ArgumentsAggregator { | ||
|
||
public SimpleAggregator() { | ||
// recreate default constructor to prevent compiler warning | ||
} | ||
|
||
@Override | ||
public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) | ||
throws ArgumentsAggregationException { | ||
Class<?> type = context.getParameter().getType(); | ||
Set<Constructor<?>> constructors = Arrays | ||
.stream(type.getConstructors()) | ||
// only if the constructor parameters and the supplied values are equal length | ||
.filter(constructor -> constructor.getParameterCount() == accessor.size()) | ||
.collect(toUnmodifiableSet()); | ||
if (constructors.isEmpty()) | ||
throw new ArgumentsAggregationException(format( | ||
"Could not aggregate arguments, no public constructor with %d parameters was found.", accessor.size())); | ||
return tryEachConstructor(constructors, accessor); | ||
} | ||
|
||
private Object tryEachConstructor(Set<Constructor<?>> constructors, ArgumentsAccessor accessor) { | ||
Object value = null; | ||
List<Constructor<?>> matchingConstructors = new ArrayList<>(); | ||
for (Constructor<?> constructor : constructors) { | ||
try { | ||
Object[] arguments = new Object[accessor.size()]; | ||
for (int i = 0; i < accessor.size(); i++) { | ||
// can't just check against types explicitly because JUnit might be able to convert to | ||
// the types that we need, so we have to "force" that by using ArgumentsAccessor::get | ||
// which invokes JUnit's built-in ArgumentConverter | ||
// we also wrap primitive types to avoid casting problems - Java does auto unboxing later | ||
arguments[i] = accessor.get(i, PioneerUtils.wrap(constructor.getParameterTypes()[i])); | ||
} | ||
value = constructor.newInstance(arguments); | ||
matchingConstructors.add(constructor); | ||
} | ||
catch (Exception ignored) { | ||
// continue, we throw an exception if no matching constructor is found | ||
} | ||
} | ||
if (value == null) | ||
throw new ArgumentsAggregationException( | ||
"Could not aggregate arguments, no matching public constructor was found."); | ||
if (matchingConstructors.size() > 1) | ||
throw new ArgumentsAggregationException( | ||
format("Could not aggregate arguments. Expected only one matching public constructor but found %d: %s", | ||
matchingConstructors.size(), matchingConstructors)); | ||
return value; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.