Having a hard time figuring out how to utilize @ForAll
in jqwik on a @Provide
function accepting a collection.
Consider:
// domain model
public class Name {
public final String first;
public final String last;
public Name(String f, String l) {
this.first = f;
this.last = l;
}
}
// jqwik domain context
public class NameDomain extends DomainContextBase {
@Provide
public Arbitrary<Name> arbName() {
return Combinators.combine(
Arbitraries.strings().alpha(),
Arbitraries.strings().alpha()
).as(Name::new);
}
}
// properties test
public class NameProperties {
// obviously a made-up use case, just demonstrating the issue
@Provide
@Domain(NameDomain.class)
public Arbitrary<Set<String>> namesToParse(
@ForAll @Size(min = 1, max = 4) Set<Name> names) {
// ... code here
}
@Property
public void namesAreParsed(@ForAll("namesToParse") Set<String> names) {
// ... code here
}
}
When running this, I end up with:
net.jqwik.api.CannotFindArbitraryException: Cannot find an Arbitrary for Parameter of type [@net.jqwik.api.ForAll(value="", supplier=net.jqwik.api.ArbitrarySupplier$NONE.class) @net.jqwik.api.constraints.Size(value=0, max=4, min=1) Set] in method [public net.jqwik.api.Arbitrary mypackage.NameProperties.namesToParse(java.util.Set)]
Very similar issues attempting to use @UniqueElements List<Name>
instead. What am I missing here?
What you are missing is that the @Domain
annotation can only be applied to property methods or their container class. What should therefore work is:
@Property
@Domain(NameDomain.class)
public void namesAreParsed(@ForAll("namesToParse") Set<String> names) {
// ... code here
}
or
@Domain(NameDomain.class)
class NameProperties { ... }
That said, you should be aware that using @ForAll
params in a providing method will always use flat mapping over the injected parameters.
Don't use that if you actually want to just map over or combine the injected parameters. In that case your providing method would look something like:
@Provide
public Arbitrary<Set<String>> namesToParse() {
SetArbitrary<Name> names = Arbitraries.defaultFor(Name.class)
.set().ofMinSize(1).ofMaxSize(4);
// Code here just an example of what could be done:
return names.mapEach((Set<Name> ignore, Name n) -> n.first + " " + n.last);
}