Customization APIs
Start with set(), size(), and setNull() — these cover 90% of use cases. Move to intermediate and advanced APIs only when you need them.
What you will learn in this document
- How to easily create test data
- How to generate objects with desired values
- How to apply these customizations in real testing scenarios
Before you start
This document introduces various ways to create test data easily. Here are some common scenarios where you can use Fixture Monkey APIs:
- When you need member data of a specific age range for registration tests
- When you need a shopping cart with multiple products for order tests
- When you need orders above a certain amount for payment tests
Useful Terms to Know
- Sampling: The process of actually creating test data. Each time you call the
sample()method, new test data is generated. - Builder: A tool that helps you create objects step by step. In Fixture Monkey, you create a builder using
giveMeBuilder(). - Path Expression: A way to specify which property of an object to modify. For example, "age" refers to the age property, "items[0]" refers to the first item in a list, and "address.city" refers to the city property within an address object.
Table of Contents
- API Summary
- Using Basic APIs
- Learning Intermediate APIs
- Using Advanced APIs
- Frequently Asked Questions (FAQ)
API Summary
Basic APIs (Essential APIs for Beginners)
| API | Description | Example Scenario |
|---|---|---|
| set() | Set specific values directly | Set member's age to 20 |
| size() | Set collection sizes | Add 3 items to cart |
| setNull() | Set null values | Set email to null for withdrawn members |
Intermediate APIs (Use after getting familiar with basics)
| API | Description | Example Scenario |
|---|---|---|
| setInner() | Create reusable settings | Use same member info across multiple tests |
| setLazy() | Generate dynamic values | Create sequential order numbers |
| setPostCondition() | Create values meeting conditions | Test adults-only service |
| fixed() | Generate same values consistently | Use same test data across tests |
| limit | Set values for some elements only | Apply discount to some cart items |
Advanced APIs (For complex test scenarios)
| API | Description | Example Scenario |
|---|---|---|
| thenApply() | Set related values | Set order total as sum of item prices |
| customizeProperty() | Fine-tune property generation behavior | Filter values, transform data, generate unique values |
Using Basic APIs
giveMeBuilder vs giveMeKotlinBuilderKotlin examples in this page use giveMeKotlinBuilder for type-safe property references (e.g., setExp(Product::name, "value")), and giveMeBuilder for string-based APIs (e.g., setLazy("orderId") { ... }).
giveMeKotlinBuilder<T>()— provides type-safe methods likesetExp,sizeExp,setPostConditionExp,customizeProperty(KProperty)giveMeBuilder<T>()— provides string-based methods likeset("name", value),setLazy,setInner
Both are valid — use whichever fits your use case. For getting started quickly with string-based APIs, see the Quick Start Guide.
set()
The set() method is used to set specific values for object properties.
This is the most basic and commonly used API.
Basic Usage
- Java
- Kotlin
Member member = fixtureMonkey.giveMeBuilder(Member.class)
.set("name", "John Doe")
.set("age", 25)
.set("email", "john@test.com")
.sample();
Order order = fixtureMonkey.giveMeBuilder(Order.class)
.set("orderId", "ORDER-001")
.set("totalAmount", BigDecimal.valueOf(15000))
.sample();
then(member.getName()).isEqualTo("John Doe");
then(order.getOrderId()).isEqualTo("ORDER-001");
val member = fixtureMonkey.giveMeKotlinBuilder<Member>()
.setExp(Member::name, "John Doe")
.setExp(Member::age, 25)
.setExp(Member::email, "john@test.com")
.sample()
val order = fixtureMonkey.giveMeKotlinBuilder<Order>()
.setExp(Order::orderId, "ORDER-001")
.setExp(Order::totalAmount, BigDecimal.valueOf(15000))
.sample()
then(member.name).isEqualTo("John Doe")
then(order.orderId).isEqualTo("ORDER-001")
Values.just()
Values.just() wraps an object to use it as-is without decomposing it into individual fields internally.
Caution: You cannot set a child property after setting with Just.
Product product = fixture.giveMeBuilder(MyClass.class)
.set("options", Values.just(List.of("red", "medium", "adult"))
.set("options[0]", "blue")
.sample();
For example, the value of options[0] in MyClass will not be "blue", but will remain as the list set with Values.just().
size(), minSize(), maxSize()
The size() method is used to specify the size of collections like lists or arrays.
You can set exact sizes or specify minimum/maximum sizes.
Basic Usage
- Java
- Kotlin
Cart cart = fixtureMonkey.giveMeBuilder(Cart.class)
.size("items", 3)
.sample();
Product product = fixtureMonkey.giveMeBuilder(Product.class)
.size("reviews", 2, 4)
.sample();
then(cart.getItems()).hasSize(3);
then(product.getReviews().size()).isBetween(2, 4);
val cart = fixtureMonkey.giveMeKotlinBuilder<Cart>()
.sizeExp(Cart::items, 3)
.sample()
val product = fixtureMonkey.giveMeKotlinBuilder<Product>()
.sizeExp(Product::reviews, 2, 4)
.sample()
then(cart.items).hasSize(3)
then(product.reviews.size).isBetween(2, 4)
setNull(), setNotNull()
setNull() and setNotNull() are used to make properties null or ensure they have values.
Basic Usage
- Java
- Kotlin
Member withdrawnMember = fixtureMonkey.giveMeBuilder(Member.class)
.set("name", "John Doe")
.setNull("email")
.sample();
Order validOrder = fixtureMonkey.giveMeBuilder(Order.class)
.setNotNull("orderId")
.setNotNull("orderDate")
.sample();
then(withdrawnMember.getEmail()).isNull();
then(validOrder.getOrderId()).isNotNull();
val withdrawnMember = fixtureMonkey.giveMeKotlinBuilder<Member>()
.setExp(Member::name, "John Doe")
.setNullExp(Member::email)
.sample()
val validOrder = fixtureMonkey.giveMeKotlinBuilder<Order>()
.setNotNullExp(Order::orderId)
.setNotNullExp(Order::orderDate)
.sample()
then(withdrawnMember.email).isNull()
then(validOrder.orderId).isNotNull
Learning Intermediate APIs
setInner()
setInner() is used to create reusable settings for multiple tests.
It's useful when you need the same member or order information across different tests.
Basic Usage
- Java
- Kotlin
InnerSpec vipMemberSpec = new InnerSpec()
.property("grade", "VIP")
.property("point", 10000)
.property("joinDate", LocalDate.now().minusYears(1));
Member vipMember = fixtureMonkey.giveMeBuilder(Member.class)
.setInner(vipMemberSpec)
.sample();
then(vipMember.getGrade()).isEqualTo("VIP");
then(vipMember.getPoint()).isEqualTo(10000);
val vipMemberSpec = InnerSpec()
.property("grade", "VIP")
.property("point", 10000)
.property("joinDate", LocalDate.now().minusYears(1))
val vipMember = fixtureMonkey.giveMeBuilder<Member>()
.setInner(vipMemberSpec)
.sample()
then(vipMember.grade).isEqualTo("VIP")
then(vipMember.point).isEqualTo(10000)
setLazy()
setLazy() is used to generate different or sequential values each time.
It's useful for creating sequential order numbers or using current timestamps.
Basic Usage
- Java
- Kotlin
AtomicInteger orderCounter = new AtomicInteger(1);
Order order = fixtureMonkey.giveMeBuilder(Order.class)
.setLazy("orderId", () -> "ORDER-" + orderCounter.getAndIncrement())
.sample();
Order nextOrder = fixtureMonkey.giveMeBuilder(Order.class)
.setLazy("orderId", () -> "ORDER-" + orderCounter.getAndIncrement())
.sample();
then(order.getOrderId()).isEqualTo("ORDER-1");
then(nextOrder.getOrderId()).isEqualTo("ORDER-2");
val orderCounter = AtomicInteger(1)
val order = fixtureMonkey.giveMeBuilder<Order>()
.setLazy("orderId") { "ORDER-${orderCounter.getAndIncrement()}" }
.sample()
val nextOrder = fixtureMonkey.giveMeBuilder<Order>()
.setLazy("orderId") { "ORDER-${orderCounter.getAndIncrement()}" }
.sample()
then(order.orderId).isEqualTo("ORDER-1")
then(nextOrder.orderId).isEqualTo("ORDER-2")
setPostCondition()
setPostCondition() is used to generate values that meet specific conditions.
It's useful when testing services with specific requirements, like adults-only services.
If conditions are too strict, finding values might take longer. Use set() when possible.
Basic Usage
- Java
- Kotlin
Member adultMember = fixtureMonkey.giveMeBuilder(Member.class)
.setPostCondition("age", Integer.class, age -> age >= 19)
.sample();
Order largeOrder = fixtureMonkey.giveMeBuilder(Order.class)
.setPostCondition("totalAmount", BigDecimal.class,
amount -> amount.compareTo(BigDecimal.valueOf(100000)) >= 0)
.sample();
then(adultMember.getAge()).isGreaterThanOrEqualTo(19);
then(largeOrder.getTotalAmount()).isGreaterThanOrEqualTo(BigDecimal.valueOf(100000));
val adultMember = fixtureMonkey.giveMeKotlinBuilder<Member>()
.setPostConditionExp<Int>(Member::age) { it >= 19 }
.sample()
val largeOrder = fixtureMonkey.giveMeKotlinBuilder<Order>()
.setPostConditionExp<BigDecimal>(Order::totalAmount) {
it >= BigDecimal.valueOf(100000)
}
.sample()
then(adultMember.age).isGreaterThanOrEqualTo(19)
then(largeOrder.totalAmount).isGreaterThanOrEqualTo(BigDecimal.valueOf(100000))
fixed()
fixed() is used when you need the same test data every time you run your tests.
Basic Usage
- Java
- Kotlin
ArbitraryBuilder<Member> memberBuilder = fixtureMonkey.giveMeBuilder(Member.class)
.set("name", "John Doe")
.set("age", 30)
.fixed();
Member member1 = memberBuilder.sample();
Member member2 = memberBuilder.sample();
then(member1.getName()).isEqualTo(member2.getName());
then(member1.getAge()).isEqualTo(member2.getAge());
val memberBuilder = fixtureMonkey.giveMeKotlinBuilder<Member>()
.setExp(Member::name, "John Doe")
.setExp(Member::age, 30)
.fixed()
val member1 = memberBuilder.sample()
val member2 = memberBuilder.sample()
then(member1.name).isEqualTo(member2.name)
then(member1.age).isEqualTo(member2.age)
limit
limit is used when you want to set specific values for only some elements in a collection.
Basic Usage
- Java
- Kotlin
Cart cart = fixtureMonkey.giveMeBuilder(Cart.class)
.size("items", 5)
.set("items[*].onSale", true, 2)
.sample();
then(cart.getItems()).hasSize(5);
val cart = fixtureMonkey.giveMeKotlinBuilder<Cart>()
.sizeExp(Cart::items, 5)
.set("items[*].onSale", true, 2)
.sample()
then(cart.items).hasSize(5)
Using Advanced APIs
thenApply()
thenApply() is used when you need to set values based on other values in the object.
For example, setting an order's total amount based on its item prices.
Basic Usage
- Java
- Kotlin
Order order = fixtureMonkey.giveMeBuilder(Order.class)
.size("items", 3)
.thenApply((tempOrder, orderBuilder) -> {
BigDecimal total = tempOrder.getItems().stream()
.map(item -> item.getPrice())
.reduce(BigDecimal.ZERO, BigDecimal::add);
orderBuilder.set("totalAmount", total);
})
.sample();
then(order.getItems()).hasSize(3);
then(order.getTotalAmount()).isNotNull();
val order = fixtureMonkey.giveMeKotlinBuilder<Order>()
.sizeExp(Order::items, 3)
.thenApply { tempOrder, orderBuilder ->
val total = tempOrder.items
.map { it.price }
.fold(BigDecimal.ZERO, BigDecimal::add)
orderBuilder.set("totalAmount", total)
}
.sample()
then(order.items).hasSize(3)
then(order.totalAmount).isNotNull
customizeProperty()
customizeProperty() is used when you want to fine-tune how Fixture Monkey generates values for specific properties.
This is an advanced feature that gives you more control than set() by allowing transformations and filtering.
When do you need customizeProperty()?
You'll find customizeProperty() useful when:
- You want to transform generated values: "Make all names start with 'Mr.'"
- You need conditional filtering: "Only positive numbers"
- You want unique values in collections: "No duplicate items in a list"
customizeProperty requires TypedPropertySelector. This is more complex than basic APIs, so make sure you're comfortable with set(), size(), and other basic APIs first.
Simple Property Customization
- Java
- Kotlin
String expected = "transformed";
String actual = fixtureMonkey.giveMeBuilder(Member.class)
.customizeProperty(javaGetter(Member::getName), arb -> arb.map(name -> expected))
.sample()
.getName();
then(actual).isEqualTo(expected);
Member adult = fixtureMonkey.giveMeBuilder(Member.class)
.customizeProperty(javaGetter(Member::getAge), arb -> arb.filter(age -> age >= 18))
.sample();
then(adult.getAge()).isGreaterThanOrEqualTo(18);
val expected = "test"
val actual = fixtureMonkey.giveMeKotlinBuilder<Member>()
.customizeProperty(Member::name) {
it.map { _ -> expected }
}
.sample()
.name
then(actual).isEqualTo(expected)
val adult = fixtureMonkey.giveMeKotlinBuilder<Member>()
.customizeProperty(Member::age) { arb ->
arb.filter { age -> age >= 18 }
}
.sample()
then(adult.age).isGreaterThanOrEqualTo(18)
Working with Nested Properties
- Java
- Kotlin
String nestedValue = fixtureMonkey.giveMeBuilder(Order.class)
.customizeProperty(
javaGetter(Order::getCustomer).into(Customer::getName),
arb -> arb.map(name -> "Mr. " + name)
)
.sample()
.getCustomer()
.getName();
then(nestedValue).startsWith("Mr. ");
val nestedValue = fixtureMonkey.giveMeKotlinBuilder<Order>()
.customizeProperty(Order::customer into Customer::name) {
it.map { name -> "Mr. $name" }
}
.sample()
.customer
.name
Working with Collections
- Java
- Kotlin
// Customize individual elements in a collection
String firstItem = fixtureMonkey.giveMeBuilder(Cart.class)
.size("items", 3)
.customizeProperty(
javaGetter(Cart::getItems).index(String.class, 0),
arb -> arb.map(item -> "ITEM-" + item)
)
.sample()
.getItems()
.get(0);
// Make a list unique (requires experimental API)
import static com.navercorp.fixturemonkey.api.experimental.TypedExpressionGenerator.typedRoot;
List<Integer> uniqueList = fixtureMonkey.giveMeExperimentalBuilder(new TypeReference<List<Integer>>() {})
.<List<Integer>>customizeProperty(typedRoot(), CombinableArbitrary::unique)
.size("$", 10)
.sample();
// Customize individual elements in a collection
class Cart(val items: List<String>)
val firstItem = fixtureMonkey.giveMeKotlinBuilder<Cart>()
.size(Cart::items, 3)
.customizeProperty(Cart::items[0]) {
it.map { item -> "ITEM-$item" }
}
.sample()
.items[0]
// Make a list unique (requires experimental API)
import com.navercorp.fixturemonkey.api.experimental.TypedExpressionGenerator.typedRoot
val uniqueList = fixtureMonkey.giveMeExperimentalBuilder<List<Int>>()
.customizeProperty(typedRoot<List<Int>>()) {
it.unique()
}
.size(List<Int>::root, 10)
.sample()
Real-World Testing Scenarios
- Java
- Kotlin
// Testing user registration with business rules
Member validUser = fixtureMonkey.giveMeBuilder(Member.class)
.customizeProperty(javaGetter(Member::getEmail), arb ->
arb.filter(email -> email.contains("@") && email.contains("."))
.map(email -> email.toLowerCase()))
.customizeProperty(javaGetter(Member::getAge), arb ->
arb.filter(age -> age >= 18 && age <= 120))
.sample();
// Testing orders with minimum amounts
Order validOrder = fixtureMonkey.giveMeBuilder(Order.class)
.customizeProperty(javaGetter(Order::getTotalAmount), arb ->
arb.filter(amount -> amount.compareTo(BigDecimal.valueOf(10)) >= 0))
.sample();
// Testing user registration with business rules
class User(val email: String, val age: Int, val name: String)
val validUser = fixtureMonkey.giveMeKotlinBuilder<User>()
.customizeProperty(User::email) { arb ->
arb.filter { email -> email.contains("@") && email.contains(".") }
.map { email -> email.lowercase() }
}
.customizeProperty(User::age) { arb ->
arb.filter { age -> age in 18..120 }
}
.sample()
// Testing orders with minimum amounts
class Order(val totalAmount: BigDecimal)
val validOrder = fixtureMonkey.giveMeKotlinBuilder<Order>()
.customizeProperty(Order::totalAmount) { arb ->
arb.filter { amount -> amount >= BigDecimal.valueOf(10) }
}
.sample()
Important Things to Remember
-
Learn basic APIs first: Make sure you understand
set(),size(),setNull()before usingcustomizeProperty() -
Import required classes:
// For Java
import static com.navercorp.fixturemonkey.api.experimental.JavaGetterMethodPropertySelector.javaGetter;
// For experimental features
import static com.navercorp.fixturemonkey.api.experimental.TypedExpressionGenerator.typedRoot; -
Order matters:
set()will overridecustomizeProperty()// This won't work as expected
.customizeProperty(javaGetter(Member::getName), arb -> arb.map(name -> "Mr. " + name))
.set("name", "John") // This overwrites the customization above -
Keep filters reasonable: Too strict filters can cause generation to fail
// Too strict - might fail
.customizeProperty(javaGetter(Member::getAge), arb -> arb.filter(age -> age == 25))
// Better - more flexible range
.customizeProperty(javaGetter(Member::getAge), arb -> arb.filter(age -> age >= 20 && age <= 30)) -
Use for complex transformations: If you just need a specific value, use
set()instead
Frequently Asked Questions (FAQ)
Q: Which APIs should I learn first?
We recommend learning in this order:
set()- This is the most basic and commonly used API.size()- You'll need this when working with lists and arrays.setNull(),setNotNull()- Use these when handling null values.
After you're comfortable with these, you can gradually learn other APIs.
Q: How can I use the same test data across tests?
You can use fixed(). For example:
// Using same member info across tests
ArbitraryBuilder<Member> memberBuilder = fixtureMonkey.giveMeBuilder(Member.class)
.set("name", "John Doe")
.set("age", 30)
.fixed(); // Generate same data every time
Member member1 = memberBuilder.sample(); // Always same data
Member member2 = memberBuilder.sample(); // Same as member1
Q: How can I prevent incorrect values from being generated?
You can use setPostCondition() to specify value ranges or conditions:
// Age must be between 1-100
Member member = fixtureMonkey.giveMeBuilder(Member.class)
.setPostCondition("age", Integer.class, age -> age >= 1 && age <= 100)
.sample();
Q: What's the difference between set() and customizeProperty()?
set()directly assigns a specific value to a propertycustomizeProperty()modifies how the property value is generated, allowing for filtering, transformation, and conditional logic
Use set() when you know exactly what value you want. Use customizeProperty() when you need to apply transformations or filters to generated values:
// Direct assignment with set()
Member member = fixtureMonkey.giveMeBuilder(Member.class)
.set("email", "john@test.com")
.sample();
// Transformation with customizeProperty()
Member memberWithCustomEmail = fixtureMonkey.giveMeBuilder(Member.class)
.customizeProperty("email", arb -> arb.map(email -> "vip-" + email))
.sample();
Q: Can I use customizeProperty() with collections?
Yes! You can customize individual elements or the entire collection:
// Customize all elements in a list
List<String> customizedList = fixtureMonkey.giveMeBuilder(new TypeReference<List<String>>() {})
.customizeProperty("$[*]", arb -> arb.map(str -> "PREFIX-" + str))
.sample();
// Make the list unique
List<Integer> uniqueList = fixtureMonkey.giveMeBuilder(new TypeReference<List<Integer>>() {})
.<List<Integer>>customizeProperty(typedRoot(), CombinableArbitrary::unique)
.size("$", 10)
.sample();