If you mark a property parameter with an annotation that is itself marked as @GeneratorConfiguration, then if the Generator for that parameter’s type has a public method named configure that accepts a single argument of the annotation type, junit-quickcheck will call the configure method reflectively, passing it the annotation. The generator can then retain the annotation to influence the results of generate().
Note: Remember to mark your configuration annotation as @Retention(RetentionPolicy.RUNTIME). Otherwise, junit-quickcheck will not detect it.
@Target({PARAMETER, FIELD, ANNOTATION_TYPE, TYPE_USE})
@Retention(RUNTIME)
@GeneratorConfiguration
public @interface NonNegative {
// ...
}
public class IntegralGenerator extends Generator<Integer> {
private NonNegative nonNegative;
public IntegralGenerator() {
super(Arrays.asList(Integer.class, int.class));
}
@Override public Integer generate(
SourceOfRandomness random,
GenerationStatus status) {
int value = random.nextInt();
return nonNegative != null ? Math.abs(value) : value;
}
public void configure(NonNegative nonNegative) {
this.nonNegative = nonNegative;
}
}
@RunWith(JUnitQuickcheck.class)
public class Numbers {
@Property public void nonNegativity(@NonNegative int i) {
assertThat(i, greaterThanOrEqualTo(0));
}
}
A Generator can have many such configure methods.
Configuration annotations that can target type uses will be honored.
@RunWith(JUnitQuickcheck.class)
public class PropertiesOfListsOfSingleDigits {
@Property public void hold(
List<@InRange(min = "0", max = "9") Integer> digits) {
// ...
}
}
Recall that potentially many generators can satisfy a given property parameter based on its type:
@RunWith(JUnitQuickcheck.class)
public class SerializationProperties {
@Property public void hold(
@InRange(min = "0", max = "10") Serializable s) {
// ...
}
}
Only the available generators that can produce something that is java.io.Serializable and that support all the configuration annotations on a property parameter will be called on to generate a value for that parameter. So, for example, for parameter s above, generators for integral values might be called upon, whereas generators for ArrayLists would not. junit-quickcheck will complain loudly if there are no such generators available.
If you have a family of generators that can produce members of a hierarchy, you may want to ensure that all the generators respect the same attributes of a given configuration annotation. Not doing so could lead to surprising results.
Configuration annotations that are directly on a property parameter, and any configuration annotations on annotations that are directly on a property parameter (and so on…) are collected to configure the generator(s) for the parameter.
@Target({PARAMETER, FIELD, ANNOTATION_TYPE, TYPE_USE})
@Retention(RUNTIME)
@From(MoneyGenerator.class)
@InRange(min = "0", max = "20")
@Precision(scale = 2)
public @interface SmallChange {
}
@RunWith(JUnitQuickcheck.class)
public class Monies {
@Property public void hold(@SmallChange BigDecimal d) {
assertEquals(2, d.scale());
assertThat(
d,
allOf(
greaterThanOrEqualTo(BigDecimal.ZERO),
lessThanOrEqualTo(new BigDecimal("20"))));
}
}