Skip to main content
Version: v1.2.x

API reference

Condensed API surface for agents. For the reasoning about which of these to use, see Writing tests.

Entry points

CallReturnsUse when
giveMeOne(Type.class)one instancenothing needs pinning
giveMe(Type.class, n)List of nseveral instances, none pinned
giveMeBuilder(Type.class)ArbitraryBuilderanything needs pinning
giveMeBuilder(value)ArbitraryBuilder seeded from an existing objectstarting from a real instance

Generic types need a TypeReference:

fixtureMonkey.giveMeOne(new TypeReference<List<Order>>() {});

Terminal operations on a builder: sample(), sampleList(n), sampleStream().

Kotlin uses reified generics throughout — giveMeOne<Order>(), giveMe<Order>(3), giveMeBuilder<Order>(). Note giveMe<T>() with no size returns a Sequence, not a List.

Selectors

Java

Static import from com.navercorp.fixturemonkey.api.expression.JavaGetterMethodPropertySelector:

import static com.navercorp.fixturemonkey.api.expression.JavaGetterMethodPropertySelector.javaGetter;
TargetExpression
Direct propertyjavaGetter(Order::getStatus)
Nested propertyjavaGetter(Order::getCustomer).into(Customer::getName)
Collection elementjavaGetter(Order::getItems).index(Item.class, 0)
All elementsjavaGetter(Order::getItems).allIndex(Item.class)

.index and .allIndex take the element type as their first argument. Chain .into(...) after them to reach into the element.

There is a deprecated javaGetter in the ...api.experimental package. Import from ...api.expression.

Selectors are values, and into returns a new selector wrapping its receiver rather than mutating it. A path prefix that many pins share can therefore be held in a constant and reused without the accumulation hazard that makes a shared ArbitraryBuilder unsafe:

private static final JavaGetterMethodPropertySelector<JiraIssueResponse, JiraFields> FIELDS =
javaGetter(JiraIssueResponse::fields);

.set(FIELDS.into(JiraFields::status).into(JiraIssueStatus::name), "Open")

Both types are public — JavaGetterMethodPropertySelector<T, U> for a root selector, JoinJavaGetterPropertySelector<T, U> for the result of into — so either can be named in a field or a return type. into itself is a default method inherited from a non-public interface, which does not prevent calling it or declaring those types from another package.

Kotlin

Requires KotlinPlugin. Import from com.navercorp.fixturemonkey.kotlin.

TargetExpression
Direct propertysetExp(Order::status, ...)
Java-style gettersetExpGetter(Order::getStatus, ...)
Nested propertysetExp(Order::customer into Customer::name, ...)
Collection elementsetExp(Order::items[0] into Item::name, ...)
All elementssetExp(Order::items["*"] into Item::name, ...)

The index operator is overloaded: an Int selects one element, the String "*" selects all.

Every builder method has an Exp form for KProperty references and an ExpGetter form for KFunction getter references: setExp / setExpGetter, setNullExp, setNotNullExp, sizeExp, minSizeExp, maxSizeExp, setPostConditionExp.

String expressions

"status", "customer.name", "items[0].name", "items[*].name", "$" for the root. Every builder method accepts them.

Avoid them in agent-written code. They break silently on rename, and an unmatched path is ignored rather than reported unless useExpressionStrictMode() is set on the FixtureMonkey instance.

Builder methods

MethodEffect
set(selector, value)Pin to a value. An Arbitrary pins to a constrained range. A third int argument limits how many matches are affected
set(value)Pin the root object
setLazy(selector, supplier)Pin to a value resolved at sample time — a fresh value per sample()
setNull(selector) / setNotNull(selector)Force absence / presence
size(selector, n) / size(selector, min, max)Fix container size. Call before setting elements
minSize(selector, n) / maxSize(selector, n)Bound container size on one side
setPostCondition(predicate)Reject samples failing the predicate. Rejection sampling — prefer set
thenApply(biConsumer)Sample, then pin further properties from the sampled value. For derived and cross-field values
acceptIf(predicate, consumer)Apply customizations only when the sampled value matches
fixed()Freeze the whole graph to one value across samples. Rarely correct in a test
setInner(innerSpec)Customize maps and other structures path expressions cannot reach. See InnerSpec
instantiate(instantiator)Choose a constructor or factory method. See Instantiate methods
validOnly(boolean)When false, allow samples that violate Bean Validation constraints
customizeProperty(selector, customizer)Transform the CombinableArbitrary behind a property, for control set cannot express
map(mapper)Returns a new builder producing mapper applied to each sample
zipWith(other, combinator)Returns a new builder combining two builders' samples
build()Returns the underlying Arbitrary instead of sampling
copy()Returns an independent copy — the way to branch without affecting the original

Ordering: the last call on an overlapping path wins

Manipulators apply in call order, and two of the resulting rules break silently.

size must precede element writes, or the elements land on a collection that may be too short and are dropped.

And where two calls overlap, the later one decides — including across a parent and its child, where it determines whether the parent exists at all:

.setNull(javaGetter(Order::getCustomer))
.set(javaGetter(Order::getCustomer).into(Customer::getName), "Kim") // parent revived, name set

.set(javaGetter(Order::getCustomer).into(Customer::getName), "Kim")
.setNull(javaGetter(Order::getCustomer)) // parent null, the name pin is gone

The first order is what lets a shared base fixture null a subtree and have each case revive only the part it needs. The second discards the pin without an exception. Sharing setup as a method that returns a builder gives the correct order for free, because the case's own calls always come after.

ArbitraryBuilder is mutable

set, size, setNull, thenApply, fixed and the rest mutate the builder and return this, and sample() does not reset it. Only copy(), map(), and zipWith() hand back a new instance.

That makes the sharing style load-bearing. A helper method is safe because it constructs a fresh builder on every call:

private ArbitraryBuilder<Order> paidOrder() {
return fixtureMonkey.giveMeBuilder(Order.class)
.set(javaGetter(Order::getStatus), OrderStatus.PAID);
}

A shared field is not. Every pin a test adds sticks to the one instance and leaks into whatever runs next, in execution order:

private static final ArbitraryBuilder<Order> PAID_ORDER = ...; // leaks between tests

If a builder must be held, call copy() before customizing it.

Values helpers

From com.navercorp.fixturemonkey.customizer.Values:

  • Values.just(value) — place the value as-is, blocking decomposition. Use it only when fixing that exact instance is genuinely required.
  • Values.unique(supplier) — draw a distinct value per generation, for uniquely-constrained columns.

Plain set decomposes the value and regenerates its properties, so a pre-built list or nested object comes back with different inner values and nothing is raised to say so. That silence is worth recognising, but it is not by itself a reason to wrap: if the assertion does not depend on those inner values, plain set is the correct call and leaves them random. Values.just freezes a subtree, which carries the same cost as fixed() — the frozen part stops exploring — so reserve it for the case where the exact instance is the thing under test.

Constraining generated values

Random does not have to mean arbitrary. These are the APIs that narrow what generation produces, in ascending order of scope. For which one to reach for, see Constrain the values you did not pin — the short version is to take the widest scope that fits, so the fewest places restate the rule.

Per property, on the builder

CallEffect
set(selector, arbitrary)Any jqwik Arbitrary: Arbitraries.strings().numeric().ofLength(12), .alpha(), .ascii(), ofMinLength/ofMaxLength, Arbitraries.longs().greaterThan(100)
set(value)No selector — constrains the root of the builder. This is the form register uses
set(selector, Values.unique(supplier))A distinct value per generation
thenApply((sample, builder) -> ...)Derive one property from another after generation
setPostCondition(selector, type, predicate)Rejection sampling. Last resort: it regenerates until the predicate passes, so it is slow and can fail to converge

Per type, on the instance

CallEffect
register(Class, fixture -> builder)A default builder for the type and its subtypes, applied wherever the type appears
registerExactType(Class, ...) / registerAssignableType(Class, ...)The same, with the matcher stated explicitly
register(MatcherOperator, priority)Arbitrary matching, with priority — lower number wins, and equal priorities are picked randomly
registerGroup(Class...)Collect many registrations in one class: every method that takes a single FixtureMonkey parameter and returns an ArbitraryBuilder<T> is registered for T

Project-wide, through a plugin

PluginEffect
new JakartaValidationPlugin(), new JavaxValidationPlugin()Generation honours Bean Validation annotations already on the production fields — @Size, @Min, @Max, @Digits, @Pattern, @Email, @NotBlank, @NotEmpty, @Past, @Future. Prefer this to restating the same rule in test code
new JqwikPlugin().javaTypeArbitraryGenerator(...)Override the default generator for built-in types by implementing strings(), integers(), longs(), characters() and so on. monkeyStrings() returns a MonkeyStringArbitrary, whose filterCharacter(predicate) restricts the character set; the default already excludes ISO control characters
new JqwikPlugin().javaTimeTypeArbitraryGenerator(...)The same for java.time and Date — override localDates(), instants(), zonedDateTimes() and the rest. Defaults span one year either side of now
new JqwikPlugin().javaArbitraryResolver(...) / .javaTimeArbitraryResolver(...)Change how constraints are applied to those arbitraries, rather than which arbitrary is used
javaConstraintGenerator(...), pushJavaConstraintGeneratorCustomizer(...)Teach Fixture Monkey a project's own constraint annotations
new DataFakerPlugin()Realistic values — names, addresses, and similar

An unsatisfiable constraint is not silent: generation retries and then throws RetryableFilterMissException, naming the property that could not be produced.

How objects get constructed

This is the most common source of "Fixture Monkey does not work on my class". Different class shapes are constructed in completely different ways, and the mechanism is chosen at three levels of scope. Work from the narrowest scope that solves the problem.

Measure the candidates before classifying

The tables in this section classify a class by its shape, and that classification is a guess. It is right most of the time and silently wrong on precisely the cases that cost the most — a type that is built, by an introspector that never writes half of it.

The guess is avoidable. Every introspector can be run against the real class in about two seconds, using nothing but jshell and the module's test runtime classpath. Three outcomes matter per candidate: it cannot build the type at all, it builds it but leaves some properties unwritten, or it writes everything.

FixtureMonkey sut = FixtureMonkey.builder()
.objectIntrospector(candidate)
.defaultNotNull(true)
.build();

Object sample = sut.giveMeOne(Order.class); // null means this candidate cannot build it

Then read every declared field back by reflection. A field still holding its JVM default was never written — and a set targeting it would be dropped with no exception and no warning. Two details keep that inference honest:

  • Numbers and booleans need sentinels. 0 and false are legitimate random values, so an unwritten int is indistinguishable from a written one. Push a constant for each primitive and wrapper first, and the default becomes proof of absence: pushExactTypeArbitraryIntrospector(int.class, context -> new ArbitraryIntrospectorResult(CombinableArbitrary.from(7))).
  • Reference types need repetition. Some values generate as null some of the time regardless of the introspector, and defaultNotNull(true) does not always override it — ZoneId is one. Sample about five times and separate always default, which is the introspector, from sometimes default, which is nullability and needs a pin instead.

The Claude Code plugin bundles this as a ready jshell script plus a Gradle init script that prints a module's test runtime classpath without touching the build files. Where jshell is unavailable it is a JDK 9+ tool, and the classification tables below remain the fallback.

Choosing when several candidates work

More than one introspector usually writes everything, and that is not a reason to combine them:

  1. One candidate is complete for every type under test — set it globally with objectIntrospector(...). This is the common outcome.
  2. Several cover every type — take the one that names how the class is actually built: ConstructorProperties for records and immutables, Bean for JavaBeans, PrimaryConstructor for Kotlin. Rank PriorityConstructor and Jackson last, since they can succeed for reasons unrelated to the class's intended shape.
  3. Different types need different introspectors — the one covering the most types globally, plus a Level 2 override per exception. Two or three overrides beat a chain.
  4. The exceptions are too numerous to enumerate — only then a FailoverIntrospector, measured rather than assumed. Probing the chain as a single candidate turns the ordering trap into an observation: the same two introspectors leave a field unwritten in one order and write it in the other.

Level 1 — the introspector (global default)

The introspector decides how every class is built. FixtureMonkey.create() uses BeanArbitraryIntrospector, which needs a no-arg constructor and setters — that is why records and immutable classes fail out of the box.

Java

Class shapeIntrospectorRequirement
JavaBeansBeanArbitraryIntrospector.INSTANCENo-arg constructor plus setters. This is the default
Records; immutable classes with a constructorConstructorPropertiesArbitraryIntrospector.INSTANCEA record, a constructor annotated @ConstructorProperties, any constructor whose parameter names survive compilation (-parameters), or a no-arg constructor. For Lombok, set lombok.anyConstructor.addConstructorProperties=true in lombok.config
Accessible fields, no settersFieldReflectionArbitraryIntrospector.INSTANCENo-arg constructor
Builder patternBuilderArbitraryIntrospector.INSTANCEA static builder() method
Any constructor, no annotations — typically a library class you cannot modifyPriorityConstructorArbitraryIntrospector.INSTANCEUses whatever constructor is available
Genuinely mixed shapesnew FailoverIntrospector(List.of(a, b, c))Tries each in order, first success wins. A last resort — see below

KotlinKotlinPlugin sets PrimaryConstructorArbitraryIntrospector as the default, so most projects need nothing further.

SituationIntrospector
Kotlin classes, defaultPrimaryConstructorArbitraryIntrospector — populates primary-constructor parameters only
Kotlin classes that reference Java classes in the same graphKotlinAndJavaCompositeArbitraryIntrospector() — applies a Kotlin introspector to Kotlin types and a Java one to Java types. Both are configurable; it defaults to PrimaryConstructor and Bean
Kotlin properties beyond the primary constructor (fields, getters, inherited members)KotlinPropertyArbitraryIntrospector

Note the trade-off in PrimaryConstructorArbitraryIntrospector: it sees only primary-constructor parameters. A property declared in the class body or inherited from a parent is not populated — the same shape of problem as the failover trap below.

From plugins

TypesIntrospectorEnabled by
Jackson-annotated typesJacksonObjectArbitraryIntrospectornew JacksonPlugin()
Interfaces, abstract classes, sealed typesnew InterfacePlugin(); see Interface plugin
Mockito mocksMockitoIntrospector.INSTANCEfixture-monkey-mockito
Realistic names, addresses, and similar valuesDataFakerArbitraryIntrospectornew DataFakerPlugin()

When no introspector matches, interfaces with no-argument methods fall back to AnonymousArbitraryIntrospector, which proxies them.

Set the one you picked on the builder:

FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.build();

Complete inventory

objectIntrospector(...) replaces only the introspector that builds ordinary objects. Everything below the first group is wired in by the default generator or a plugin and keeps working unchanged — collections, maps, enums, java.time, and primitives are never affected by that setting, and are not something to select.

GroupIntrospectors
Selectable — object shapeBeanArbitraryIntrospector (default), ConstructorPropertiesArbitraryIntrospector, FieldReflectionArbitraryIntrospector, BuilderArbitraryIntrospector, PriorityConstructorArbitraryIntrospector, FailoverIntrospector
Selectable — KotlinPrimaryConstructorArbitraryIntrospector (KotlinPlugin default), KotlinAndJavaCompositeArbitraryIntrospector, KotlinPropertyArbitraryIntrospector
Selectable — from pluginsJacksonObjectArbitraryIntrospector, Jackson3ObjectArbitraryIntrospector, DataFakerArbitraryIntrospector, MockitoIntrospector
Automatic — containers and built-insArrayIntrospector, IterableIntrospector, IteratorIntrospector, SetIntrospector, QueueIntrospector, StreamIntrospector, OptionalIntrospector, SingleGenericCollectionIntrospector, MapIntrospector, MapEntryIntrospector, MapEntryElementIntrospector, EnumIntrospector, BooleanIntrospector, UuidIntrospector, JavaArbitraryIntrospector (primitives, String), JavaTimeArbitraryIntrospector, FunctionalInterfaceArbitraryIntrospector
Automatic — Kotlin built-ins (KotlinPlugin)PairIntrospector, TripleIntrospector, KotlinDurationIntrospector, CompanionObjectFactoryMethodIntrospector
Automatic — Jackson containersJacksonArrayArbitraryIntrospector, JacksonCollectionArbitraryIntrospector, JacksonMapArbitraryIntrospector, JsonNodeIntrospector, and the Jackson 3 equivalents Jackson3ArrayArbitraryIntrospector, Jackson3CollectionArbitraryIntrospector, Jackson3MapArbitraryIntrospector, Jackson3JsonNodeIntrospector
Automatic — validation pluginsJakartaValidationBooleanIntrospector, JavaxValidationBooleanIntrospector
Internal — driven by other APIsConstructorArbitraryIntrospector and FactoryMethodArbitraryIntrospector (used by instantiate), AnonymousArbitraryIntrospector (interface fallback), ConstantIntrospector, NullArbitraryIntrospector
Building blocks for a custom introspectorCompositeArbitraryIntrospector, MatchArbitraryIntrospector, TypedArbitraryIntrospector — see Custom introspector

If a collection, enum, or java.time value is generating incorrectly, the object introspector is not the cause. Look at the plugin set or the relevant option instead.

Prefer one introspector over a failover chain

Classify the types under test and pick the single introspector that matches. One correct introspector either works or fails loudly; a chain has an ordering that can be wrong in a way nothing reports.

When a few types do not fit, scope those individually with pushAssignableTypeArbitraryIntrospector (see Level 2) instead of adding a chain. Ten records and two JavaBeans is not a mixed codebase — it is ConstructorProperties globally plus two overrides.

Reserve FailoverIntrospector for shapes that are genuinely mixed and too numerous to enumerate.

The failover ordering trap

FailoverIntrospector stops at the first introspector that succeeds, and "succeeds" means "produced an object" — not "populated everything you pinned".

ConstructorPropertiesArbitraryIntrospector writes only what the chosen constructor takes as parameters. A field outside that list — a JPA id, createdAt, an audit column — is never written. It stays null, and a set targeting it is dropped with no exception and no warning.

This fires more often than the annotation requirement suggests, because a constructor also qualifies when its parameter names merely survive compilation. Spring Boot's build plugins add -parameters by default, so an ordinary public constructor is enough:

// Organization has a public 4-arg constructor (name, description, code, userId).
// ConstructorProperties builds from it and reports success, so failover never
// reaches FieldReflection — and `id` is not a constructor parameter.
new FailoverIntrospector(List.of(
ConstructorPropertiesArbitraryIntrospector.INSTANCE, // wins; id stays null
FieldReflectionArbitraryIntrospector.INSTANCE))

builder.set(javaGetter(Organization::getId), 1L) // silently ignored

The failure then surfaces nowhere near its cause — as an NPE inside the production code under test, on a getter the test never mentioned. The stack trace looks like a service bug.

Order the strictest introspector first, so the permissive one only sees what the strict one rejected:

// FieldReflection needs a no-arg constructor, so records fail here and fall
// through correctly. Classes that have one get every field written, including id.
new FailoverIntrospector(List.of(
FieldReflectionArbitraryIntrospector.INSTANCE,
ConstructorPropertiesArbitraryIntrospector.INSTANCE))

useExpressionStrictMode() does not catch this. The path resolves to a real property; the introspector simply never writes it.

Which order is correct is measurable rather than arguable — probe the chain as a single candidate, as described above, and read off which fields each ordering leaves unwritten.

After configuring a FixtureMonkey, sample one instance of each type and assert that the pins landed. A dropped pin is silent, so this throwaway check is the cheapest way to find it:

Organization probe = organization(1L, "DEPT");
assertThat(probe.getId()).isEqualTo(1L); // fails here, not 200 lines away

Level 2 — per type

When one class does not fit the global introspector, do not change the global one. Override that class only:

FixtureMonkey.builder()
.pushAssignableTypeArbitraryIntrospector(Order.class, BuilderArbitraryIntrospector.INSTANCE)
.build();

register sets generation rules for a type — including an instantiate — across every test using that instance. registerGroup does the same property by property. Reach for either only when the rule genuinely belongs everywhere; a rule that exists for one scenario belongs in that test.

Level 3 — per builder, with instantiate

The narrowest scope: this builder, this test. Use it when a class has several constructors, when a factory method is the right entry point, or when one test needs a different construction path than the rest.

Java — static imports from com.navercorp.fixturemonkey.api.instantiator.Instantiator:

import static com.navercorp.fixturemonkey.api.instantiator.Instantiator.constructor;
import static com.navercorp.fixturemonkey.api.instantiator.Instantiator.factoryMethod;
GoalCall
Use a constructor.instantiate(constructor())
Pick one overload by signature.instantiate(constructor().parameter(String.class).parameter(int.class))
Name a parameter so set can reach it.instantiate(constructor().parameter(String.class, "name"))
Use a static factory method.instantiate(factoryMethod("create"))
Factory method with a chosen signature.instantiate(factoryMethod("create").parameter(String.class))
Constructor, then fill remaining state via fields.instantiate(constructor().field())
Constructor, then fill remaining state via setters.instantiate(constructor().javaBeansProperty())
Apply to a nested type rather than the root.instantiate(Address.class, constructor())

Parameter names matter: .parameter(String.class, "name") is what makes set("name", ...) resolve for a constructor parameter that carries no name at runtime.

KotlininstantiateBy, imported from com.navercorp.fixturemonkey.kotlin.instantiator.instantiateBy (requires KotlinPlugin):

.instantiateBy { constructor() }
.instantiateBy { constructor<Product> { property() } }
.instantiateBy { factory("create") }

Diagnosing a construction failure

SymptomLikely causeFix
A set is ignored with no error and the value stays nullThe introspector never writes that propertyDo not hunt for an exception — suspect the introspector. With ConstructorProperties, only constructor parameters are written
NPE in production code on a field the test pinnedSame cause; the pin was dropped and the symptom is far from itSample a probe instance and assert the pin landed
Some fields populated, others nullA constructor-based introspector wrote only its parameter listUse an introspector that writes fields, or override that type
All properties null or defaultIntrospector cannot write to the classMatch the introspector to the class shape (level 1)
Fails on a record or immutable classDefault BeanArbitraryIntrospector needs settersConstructorPropertiesArbitraryIntrospector
Works for most classes, fails for oneGlobal introspector is right, one class differsOverride that type (level 2) or instantiate (level 3)
Wrong constructor pickedMultiple overloadsconstructor().parameter(...) naming the signature
set on a constructor parameter is ignoredParameter name not available at runtime.parameter(Type.class, "name"), or compile with -parameters
Lombok class not generatingNo @ConstructorPropertieslombok.anyConstructor.addConstructorProperties=true

See Introspector and Instantiate methods for the full treatment.

Other setup

SituationOption
Kotlin.plugin(KotlinPlugin())
Jackson-annotated types.plugin(new JacksonPlugin())
Bean Validation annotations should be honoured.plugin(new JakartaValidationPlugin())
Nulls are getting in the way.defaultNotNull(true), or defaultNullInjectGenerator for a rate other than the 0.2 default
Interfaces and sealed types.plugin(new InterfacePlugin())

See Fixture Monkey options for the full option set, and the Plugins section for each plugin — Kotlin is the one most agents need.

Reproducibility

fixture-monkey-junit-jupiter provides @Seed(1234L). It fixes the seed for one test method and logs the seed of any failing test so a CI failure can be replayed. Use it to diagnose an intermittent failure, not to make one go away.