Testland
Browse all skills & agents

jqwik-testing

Authors property-based tests for the JVM (Java + Kotlin) using jqwik - wires `@Property` test methods, `@ForAll` parameter annotations, `Arbitraries.integers/strings/etc` generators, custom `@Provide` arbitraries, and the JUnit 5 platform integration. Use when a JVM project needs PBT - alternative to JUnit-QuickCheck and Vavr's property-checking; tightly integrates with JUnit 5 so property tests run alongside conventional unit tests in the same Maven / Gradle pipeline.

Install with skills.sh (any agent)

npx skills add testland/qa --skill jqwik-testing
View source

jqwik-testing

Overview

jqwik is a JVM property-based testing library for Java and Kotlin that integrates with JUnit 5, so property tests run alongside conventional unit tests in the same Maven / Gradle pipeline (jqwik-home (opens in new window)).

When to use

  • A JVM project (Java / Kotlin / Scala) tests via JUnit 5 and needs PBT alongside the existing unit tests.
  • The team finds JUnit-QuickCheck or Vavr's PBT awkward; jqwik's JUnit 5 integration is more idiomatic.
  • Properties hold for parsers / encoders / round-trip transforms.

Step 1 - Install

Maven:

<dependency>
  <groupId>net.jqwik</groupId>
  <artifactId>jqwik</artifactId>
  <version>1.9.3</version>
  <scope>test</scope>
</dependency>

The artifact pulls in the JUnit 5 platform dependency transitively. Version note: references/jqwik-reference.md.

Gradle:

testImplementation 'net.jqwik:jqwik:1.9.3'

test {
    useJUnitPlatform()
}

Step 2 - Basic property

Per jqwik-home (opens in new window):

import net.jqwik.api.*;

class CartProperties {

    @Property
    boolean addingItemIncreasesCount(@ForAll int qty) {
        Cart cart = new Cart();
        cart.addItem(new Item("BOOK-001", qty));
        return cart.itemCount() == qty;
    }
}

The @Property annotation marks a property test (JUnit 5 hooks in via the platform). @ForAll on a parameter means "jqwik generates values for this." The method returns boolean (or void with assertions).

By default jqwik runs each property with 1000 generated cases (more than Hypothesis's 100 or proptest's 256).

Step 3 - Constrained generators

@Property
boolean validQtyStaysInRange(@ForAll @IntRange(min = 1, max = 100) int qty) {
    Cart cart = new Cart();
    cart.addItem(new Item("BOOK-001", qty));
    return cart.itemCount() >= 1 && cart.itemCount() <= 100;
}

Constraint annotations live in net.jqwik.api.constraints.* (@IntRange, @LongRange, @StringLength, @Size, @NotEmpty, @Email, @Positive, ...). Full table: references/jqwik-reference.md.

Step 4 - Custom arbitraries via @Provide

class UserProperties {

    @Property
    boolean userJsonRoundTrip(@ForAll("validUser") User u) {
        String json = mapper.writeValueAsString(u);
        User back = mapper.readValue(json, User.class);
        return u.equals(back);
    }

    @Provide
    Arbitrary<User> validUser() {
        Arbitrary<Long> id = Arbitraries.longs().between(1, 1_000_000);
        Arbitrary<String> email = Arbitraries.strings()
            .alpha().ofMinLength(3).ofMaxLength(10)
            .map(s -> s + "@example.com");
        Arbitrary<Integer> age = Arbitraries.integers().between(18, 100);
        return Combinators.combine(id, email, age).as(User::new);
    }
}

The @Provide method returns an Arbitrary<T>; its name (or the method name when omitted) is referenced from @ForAll("...").

Combinators.combine(...) is the canonical way to assemble a multi-field arbitrary; .as(...) constructs the target object from the parts.

Step 5 - Arbitraries catalog

Per jqwik-home (opens in new window), the Arbitraries API provides built-in generators:

Arbitraries.integers().between(0, 100);            // bounded int
Arbitraries.strings().alpha().ofLength(8);         // fixed-length alpha string
Arbitraries.of(Status.ACTIVE, Status.SUSPENDED);   // enum-like
Arbitraries.integers().list().ofMinSize(1).ofMaxSize(100);   // List<Integer>

Half-bounded generators, sets, maps, and subsetOf: references/jqwik-reference.md.

Step 6 - Statistics + classification

@Property
@Statistics
boolean evenAndOddDistribution(@ForAll int n) {
    Statistics.label("evenness").collect(n % 2 == 0 ? "even" : "odd");
    return true;
}

Statistics output reports the distribution of generated cases - useful for verifying the strategy generates the intended mix.

Step 7 - Configuration

@Property(tries = 5000, shrinking = ShrinkingMode.FULL, edgeCases = EdgeCasesMode.MIXIN)
boolean expensiveProperty(@ForAll long n) {
    // ...
}
FieldDefaultUse
tries1000More for higher confidence.
shrinkingFULLFULL (default) / BOUNDED / OFF.
edgeCasesMIXINMix in edge cases (0, MAX, MIN, ε, etc.) by default.
seedrandomFixed seed for CI determinism.
maxDiscardRatio5Cap on assume() rejection ratio.

For CI determinism:

@Property(seed = "42")
boolean reproducibleProperty(@ForAll int n) { /* ... */ }

Or globally via jqwik.config.properties:

defaultSeed = 42
defaultTries = 1000

Step 8 - CI integration

mvn test                  # runs JUnit 5 tests including @Property
gradle test               # same

No additional config beyond JUnit 5 platform.

References

  • references/jqwik-reference.md - constraint annotations, Arbitraries catalog, anti-patterns, limitations, version note.
  • jh (opens in new window) - jqwik overview: @Property, @ForAll, @Provide, Arbitraries API, JUnit 5 integration.
  • hypothesis-testing, fast-check-testing, proptest-testing, quickcheck-testing - per-language siblings.

jqwik reference

Full lookup tables and maintenance notes for jqwik-testing. Source: jqwik.net (opens in new window).

Version

Pinned to jqwik 1.9.3 (latest at source-fetch); the artifact pulls in the JUnit 5 platform dependency transitively. Check jqwik.net before bumping.

Constraint annotations

In net.jqwik.api.constraints.*:

AnnotationPurpose
@IntRange(min, max)Integers in range
@LongRange(min, max)Longs in range
@DoubleRange(min, max)Doubles in range
@StringLength(min, max)Strings of bounded length
@AlphaChars / @NumericCharsString character class
@Size(min, max)Collection size
@NotEmptyNon-empty collection / string
@NotBlankNon-whitespace string
@EmailEmail-shaped string
@MinList / @MaxListList bounds
@UniqueElementsUnique-element collections
@Positive / @NegativePositive / negative number

Arbitraries catalog

Arbitraries.integers()                       // any int
    .between(0, 100)                         // bounded
    .greaterOrEqual(0);                       // half-bounded

Arbitraries.strings()                        // any string
    .alpha()                                  // alphabetic only
    .ofLength(8);                             // fixed length

Arbitraries.of(Status.ACTIVE, Status.SUSPENDED, Status.NOT_VERIFIED);   // enum-like

Arbitraries.subsetOf(allRoles);              // subset of a known set

For collections:

Arbitraries.integers().list()                // List<Integer>
    .ofMinSize(1).ofMaxSize(100);

Arbitraries.strings().set();                  // Set<String>

Arbitraries.maps(
    Arbitraries.strings().alpha(),
    Arbitraries.integers().between(0, 100)
);

Anti-patterns

Anti-patternWhy it failsFix
Returning void without assertionsTest always passes; property is silent.Return boolean OR throw on failure (e.g. JUnit assertTrue).
@Provide arbitrary that filters most casesDiscard ratio exceeded; jqwik fails the property.Constrain at the Arbitrary level (Step 3 annotations + .between(...)).
Sharing mutable state across @Property iterationsTest depends on iteration order; flaky.Construct fresh state per call (within the property method body).
Hard-coded seed in production propertyLoses randomization; misses regressions.Random seed locally; fixed seed in CI only (Step 7).
tries = 100_000 on a slow propertyCI never finishes.Budget per total runtime / tries.
Asserting on specific values inside the propertyDefeats PBT.Properties assert relationships.
Skipping @Provide for one-off domain objectsInline construction is verbose; harder to reuse.Move to @Provide even for one-property arbitraries.

Limitations

  • JVM startup cost. Each property test pays JVM start; 1000 iterations of fast tests are still seconds, not milliseconds.
  • Shrinking complex types is slow. BOUNDED shrinking caps iterations; OFF skips entirely.
  • Kotlin support requires extra setup. Per jqwik.net (opens in new window), Kotlin is supported but requires the jqwik-kotlin extension.
  • No native async support. Test reactive / coroutine code through runBlocking (Kotlin) or explicit Future.get().
  • JUnit 5 only. No JUnit 4 platform integration.

Related skills

fast-check-testing

Authors property-based tests in JavaScript / TypeScript using fast-check - wires `fc.assert(fc.property(arbitrary, ...))`, picks arbitraries (`fc.integer`, `fc.string`, `fc.array`, `fc.tuple`, `fc.record`), uses `.map()` / `.chain()` / `.filter()` to build domain arbitraries, and integrates with Jest / Vitest / Mocha / Jasmine / AVA / Tape. Use when a JS/TS codebase needs PBT to catch edge cases - fast-check has been used to find bugs in major libraries (`query-string`, etc.) and is trusted by Jest, Jasmine, fp-ts, Ramda.

hypothesis-testing

Authors property-based tests in Python using Hypothesis - wires `@given` with `strategies` (`st.integers`, `st.text`, `st.lists`, `st.from_regex`, `st.composite`), uses `assume()` / `.filter()` for preconditions, configures via `@settings(max_examples=..., deadline=...)`, and exploits Hypothesis's automatic shrinking to find the falsifying example. Integrates with pytest fixtures + parametrize. Use when a Python project needs PBT to catch edge cases the example-based tests miss - bug clusters around input ranges / boundary values / interaction between fields.

proptest-testing

Authors property-based tests in Rust using proptest - wires the `proptest!` macro, defines strategies (`prop::collection::vec`, type-driven `any` strategies, regex-based string strategies), uses the strategy-per-value model (vs QuickCheck's per-type) for flexible composition, and exploits proptest's automatic shrinking + persistence of failed cases (regression test artifact). Use when a Rust codebase needs PBT - pairs especially well with parsers, serializers, and any function with a structured input domain.

quickcheck-testing

Authors property-based tests for Haskell using QuickCheck (the original PBT library) and for Scala via ScalaCheck (the JVM port) - wires `quickCheck` (Haskell) / `forAll` (ScalaCheck) drivers, defines `Arbitrary` instances or generators, uses `shrink` to find minimal counterexamples, and integrates with HSpec / Tasty (Haskell) or specs2 / ScalaTest. Use when the codebase is Haskell or Scala and the team wants the canonical PBT library that the entire family (Hypothesis / fast-check / proptest / jqwik) was inspired by.