Install
1
Run in your terminal$
npx claudepluginhub nwave-ai/nwave --plugin nwTool Access
This skill uses the workspace's default tool permissions.
Skill Content
PBT JVM -- jqwik (Java/Kotlin) + ScalaCheck + ZIO Test
Framework Selection
| Framework | Language | Shrinking | Stateful | Choose When |
|---|---|---|---|---|
| jqwik | Java/Kotlin | Integrated | Yes (actions) | Java/Kotlin projects (recommended default) |
| ScalaCheck | Scala | Type-based | Yes (Commands) | Scala projects (established choice) |
| ZIO Test | Scala | Integrated | Via effects | ZIO-based Scala projects |
Quick Start (jqwik)
import net.jqwik.api.*;
class SortProperties {
@Property
void sortPreservesLength(@ForAll List<Integer> list) {
List<Integer> sorted = new ArrayList<>(list);
Collections.sort(sorted);
Assertions.assertEquals(list.size(), sorted.size());
}
}
// Run: ./gradlew test (or mvn test)
Generator (Arbitrary) Cheat Sheet (jqwik)
@ForAll int x // any int
@ForAll @IntRange(min = 0, max = 99) int x
@ForAll @StringLength(min = 1, max = 50) String s
@ForAll @Size(min = 1, max = 10) List<Integer> list
// Custom provider
@Provide
Arbitrary<String> emails() {
return Arbitraries.strings().alpha().ofMinLength(1).ofMaxLength(10)
.map(name -> name + "@example.com");
}
// Combinators
Arbitraries.integers().between(0, 100)
Arbitraries.of("a", "b", "c")
Arbitraries.frequencyOf(Tuple.of(80, Arbitraries.integers()), Tuple.of(20, Arbitraries.just(0)))
// Combine
Combinators.combine(
Arbitraries.strings().alpha().ofMinLength(1),
Arbitraries.integers().between(1, 120)
).as((name, age) -> new User(name, age))
// Recursive
Arbitraries.recursive(
() -> Arbitraries.of(JsonValue.NULL, JsonValue.TRUE),
inner -> Arbitraries.maps(Arbitraries.strings(), inner).map(JsonValue::fromMap),
5
)
Stateful Testing (jqwik)
@Property
void storeMatchesModel(@ForAll("storeActions") ActionSequence<MyStore> actions) {
actions.run(new MyStore());
}
@Provide
ActionSequenceArbitrary<MyStore> storeActions() {
return Arbitraries.sequences(
Arbitraries.oneOf(
Combinators.combine(Arbitraries.strings(), Arbitraries.integers())
.as(PutAction::new),
Arbitraries.strings().map(GetAction::new)
)
);
}
class PutAction implements Action<MyStore> {
final String key; final int value;
PutAction(String key, int value) { this.key = key; this.value = value; }
@Override public MyStore run(MyStore store) { store.put(key, value); return store; }
}
Quick Start (ScalaCheck)
import org.scalacheck.Prop.forAll
val propSortLength = forAll { (xs: List[Int]) => xs.sorted.length == xs.length }
// With ScalaTest
class SortSpec extends AnyFunSuite with ScalaCheckPropertyChecks {
test("sort idempotent") { forAll { (xs: List[Int]) => xs.sorted.sorted shouldBe xs.sorted } }
}
ScalaCheck Generators
Gen.choose(0, 100) // bounded int
Gen.alphaStr // alphabetic string
Gen.listOf(Gen.posNum[Int]) // list
Gen.oneOf(Gen.const(1), Gen.const(2)) // union
Gen.frequency((80, Gen.alphaChar), (20, Gen.numChar))
Gen.recursive[Tree](gen =>
Gen.oneOf(Gen.const(Leaf), for { l <- gen; r <- gen; v <- Gen.posNum[Int] } yield Node(v, l, r))
)
ScalaCheck Stateful (Commands)
object StoreSpec extends Commands {
type State = Map[String, Int]; type Sut = MyStore
def genCommand(state: State): Gen[Command] = Gen.oneOf(
for { k <- Gen.alphaStr; v <- Gen.posNum[Int] } yield Put(k, v),
Gen.oneOf(state.keys.toSeq).map(Get(_))
)
}
Quick Start (ZIO Test)
import zio.test._
test("sort preserves length") {
check(Gen.listOf(Gen.int)) { xs => assertTrue(xs.sorted.length == xs.length) }
}
ZIO Test Generator Cheat Sheet
Gen.int // any Int
Gen.int(0, 100) // bounded
Gen.double // any Double
Gen.string // any String
Gen.alphaNumericString
Gen.boolean
Gen.listOf(Gen.int) // List[Int]
Gen.setOf(Gen.string) // Set[String]
Gen.mapOf(Gen.string, Gen.int) // Map[String, Int]
Gen.option(Gen.int) // Option[Int]
Gen.oneOf(Gen.const(1), Gen.const(2)) // union
Gen.weighted((Gen.int, 80.0), (Gen.const(0), 20.0)) // weighted
// Custom
val genUser = for {
name <- Gen.alphaNumericString
age <- Gen.int(1, 120)
} yield User(name, age)
ZIO Test stateful testing: Use ZIO.stateful with Ref-based model state in effect composition.
Test Runner Integration
<!-- jqwik (Maven) -->
<dependency>
<groupId>net.jqwik</groupId><artifactId>jqwik</artifactId>
<version>1.8.0</version><scope>test</scope>
</dependency>
// ScalaCheck (build.sbt)
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.17.0" % Test
// ZIO Test: "dev.zio" %% "zio-test" % "2.x" % Test
Unique Features
jqwik
- Edge cases: Automatically tests boundary values (0, MIN/MAX, empty)
- @StatisticsReport: Shows distribution of generated values
- Domains: Group related arbitraries into reusable contexts
- Kotlin support: Works natively via JUnit 5
ScalaCheck
- Type-class based:
Arbitrary[T]for automatic derivation - Parallel Commands: Stateful testing with parallel execution
- Shrink[T]: Separate shrink type class (can shrink past generator constraints)
Similar Skills
Stats
Parent Repo Stars299
Parent Repo Forks37
Last CommitMar 20, 2026