(Context: my background in property-based testing is mostly from scala's scalacheck library, the use of some of the types and annotations in jqwik feels a bit different and there are a couple paradigms I can't quite get the hang of yet.)
I'm not sure how best to combine existing Arbitrary
definitions for primitive types to produce a re-usable (needed for more than a single @Property
or test class) Arbitrary that is built atop other Aribtrary
definitions I've defined.
Given this is probably much clearer with an illustration:
// assume this has a builder pattern or all-args constructor.
// consider this is some sort of core domain object that I need in various
// test suites
public class MyComplexClass {
private final String id; // positive-integer shaped
private final String recordId; // uuid-shaped
private final String creatorId; // positive-integer shaped
private final String editorId; // positive-integer shaped
private final String nonce; // uuid-shaped
private final String payload; // random string
}
My instinct is to define Aribrary<String>
that produces UUID-like strings and another that produces positive integer strings, something like:
public class MyArbitraries {
public Arbitrary<String> arbUuidString() {
return Combinators.combine(
Arbitraries.longs(), Arbitraries.longs(), Arbitraries.of(Set.of('8', '9', 'a', 'b')))
.as((l1, l2, y) -> {
StringBuilder b = new StringBuilder(new UUID(l1, l2).toString());
b.setCharAt(14, '4');
b.setCharAt(19, y);
return UUID.fromString(b.toString());
});
}
public Arbitrary<String> arbNumericIdString() {
return Arbitraries.shorts().map(Math::abs).map(i -> "" + i);
}
}
But then I'm not sure the best way to utilize these to produce an Arbitrary< MyComplexClass>
. I'd want something like:
public class MyDomain extends DomainContextBase {
@Provider
public Arbitrary<MyComplexClass> arbMyComplexClass() {
return Builders.withBuilder(MyComplexClass::newBuilder)
// best way to reference these?!
.use(arbNumericIdString()).in(MyComplexClass.Builder::setId)
.use(arbUuidString()).in(MyComplexClass.Builder::setCreatorId)
// etc.
.build(MyComplexClass.Builder::build);
}
}
My understanding here is:
- I cannot use
@ForAll
to 'inject' or provide these Arbitraries asForAll
is only supported in@Property
-annotated methods - I cannot use
@Domain
here for similar reasons - I can't really use
ArbitrarySupplier
or similar as there is no obvious 'type' here, it's mostly just a bunch of strings
Is the best option to just create static Arbitrary<String>
functions and call them directly?
One initial comment:
@ForAll
also works in methods annotated with@Provide
and in domains. Here's a simple example:Maybe the confusing thing is that the string reference in
@ForAll("myString")
is only evaluated locally (the class itself, superclasses and containing classes). This is by purpose, in order to prevent string-based reference magic; having to fall back to strings in the first place - since method refs cannot be used in Java annotations - is already bad enough.As for your concrete question:
I consider that a "good enough" approach for sharing generators within a single domain or when you have several related domains that inherit from a common superclass.
When you want to share generators across unrelated domains, you'll have to either:
RecordId
,UUIDString
etc. Then you can use domains (or registeredArbitraryProvider
s to generate based on type.This examples also shows how one domain (
MyNumbers
) can be used in another domain (MyDomain
) through annotating the domain implementation class and either having a parameter being injected or useArbitraries.defaultFor(TypeProvidedInOtherDomain.class)
.But maybe there's a useful feature for sharing arbitraries missing in jqwik. The jqwik team's happy about any suggestion.