1

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?

drobert
  • 1,230
  • 8
  • 21

1 Answers1

1

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);
}
johanneslink
  • 4,877
  • 1
  • 20
  • 37
  • Ok, this is helpful. It still seems like I'm missing a simple way to combine a few boilerplate arbitrary definitions. Consider I need an `Arbitrary` type comprised of a few uuid-like strings, some positive integers, and a few alphanumeric strings. It would be pretty annoying to copy-paste the logic of each of these for each property of the class, and I can't delegate to `Arbitraries` or similar becaues the types overlap (they're basically all `Aribtrary` or `Arbitrary` – drobert Nov 22 '22 at 16:26
  • **Update**: actually, `ArbitrarySupplier` might be good for this. Trying it out. – drobert Nov 22 '22 at 16:32
  • No, can't quite get the feel for it. I need to combine myriad `Aribtrary`s together in some other provider/means of generating an Arbitrary. It feels like the primary design is to have a single, static type Arbitrary that can be called via Arbitraries.defaultFor (won't work here) or do all the combining within a @Property , which also doesn't really allow for re-use. I'll ask this in another question. – drobert Nov 22 '22 at 16:58