I am trying jqwik (version 1.5.1) and I read from the documentation that I can create an Arbitrary
whose generated value depends on the one supplied by another Arbitrary
, specifically using the flatMap
function.
My actual goal is different, but based on this idea: I need 2 Arbitrary
s that always generate different values for a single test. This is what I tried:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
var secondArbitrary = firstArbitrary.flatMap(first ->
Arbitraries.integers().between(1, Integer.MAX_VALUE).filter(i -> !i.equals(first)));
return Combinators.combine(firstArbitrary, secondArbitrary).as(Tuple::of);
}
@Property
public void test(@ForAll("getValues") Tuple.Tuple2<Integer, Integer> values) {
assertThat(values.get1()).isNotEqualTo(values.get2());
}
And it immediately fails with this sample:
Shrunk Sample (1 steps)
-----------------------
arg0: (1, 1)
Throwing an AssertionError
of course:
java.lang.AssertionError:
Expecting:
1
not to be equal to:
1
I expected the filter
function would have been enough to exclude the generated value produced by the firstArbitrary
but it seems like it is not even considered, or more likely it does something else. What am I missing? Is there an easier way to make sure that, given a certain number of integer
generators, they always produce different values?
The general idea of one generated value influencing the next generation step through flatMap
is right. The thing you are missing is that you loose this coupling by combining firstArbitrary
and secondArbitrary
outside of the flat mapping scope. The fix is minor:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.flatMap(
first -> Arbitraries.integers().between(1, Integer.MAX_VALUE)
.filter(i -> !i.equals(first))
.map(second -> Tuple.of(first, second))
);
}
That said there are more - I'd argue simpler - ways to achieve your goal:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.tuple2().filter(t -> !t.get1().equals(t.get2()));
}
This gets rid of flat mapping, which means less effort while shrinking for jqwik.
Another possible solution:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.list().ofSize(2).uniqueElements().map(l -> Tuple.of(l.get(0), l.get(1)));
}
This one might seem a bit involved, but it has the advantage that no flat mapping and no filtering is being used. Filtering often reduces performance of generation, edge cases, exhaustive generation and shrinking. That's why I steer clear of filtering whenever I can without too much hassle.