Overview
When you're starting with Fixture Monkey, using options can seem overwhelming. This guide helps you understand how to navigate the options and where to start.
Options vs ArbitraryBuilder API
There are two main ways to configure test data in Fixture Monkey:
-
Options
- Set during FixtureMonkey instance creation
- Define global rules that apply to all test data generation
- Reusable configurations
-
ArbitraryBuilder API
- Set during individual test data creation
- One-time settings needed for specific test cases
- More fine-grained control
Example:
// Using options - applies to all Product instances
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultNotNull(true) // Set all fields to non-null
.register(Product.class, fm -> fm.giveMeBuilder(Product.class)
.size("items", 3)) // items always has 3 elements
.build();
// Using ArbitraryBuilder API - applies only to this test
Product specificProduct = fixtureMonkey.giveMeBuilder(Product.class)
.set("name", "Test Product") // Set name just for this test
.set("price", 1000) // Set price just for this test
.sample();
Why Should You Use Options?
There are important reasons to use options:
1. Test Data Consistency
- Problem: Need to apply the same rules across multiple tests
// Without options - need to repeat settings in every test
Product product1 = fixtureMonkey.giveMeBuilder(Product.class)
.set("price", Arbitraries.longs().greaterThan(0))
.sample();
Product product2 = fixtureMonkey.giveMeBuilder(Product.class)
.set("price", Arbitraries.longs().greaterThan(0))
.sample();// With options - set once, apply everywhere
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.register(Product.class, fm -> fm.giveMeBuilder(Product.class)
.set("price", Arbitraries.longs().greaterThan(0)))
.build();
Product product1 = fixtureMonkey.giveMeOne(Product.class); // Automatically positive price
Product product2 = fixtureMonkey.giveMeOne(Product.class); // Automatically positive price
2. Test Maintainability
- Problem: Need to modify all tests when rules change
// Using options - manage rules in one place
public class TestConfig {
public static FixtureMonkey createFixtureMonkey() {
return FixtureMonkey.builder()
.defaultNotNull(true)
.register(Product.class, fm -> fm.giveMeBuilder(Product.class)
.set("price", Arbitraries.longs().greaterThan(0))
.set("stock", Arbitraries.integers().greaterThan(0)))
.register(Order.class, fm -> fm.giveMeBuilder(Order.class)
// Add orderRules() builder chaining here
)
.build();
}
}
3. Domain Rule Application
- Problem: Need to apply business rules to test data
// Applying domain rules through options
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.register(Order.class, fm -> fm.giveMeBuilder(Order.class)
.thenApply((order, b) -> {
b.set("totalAmount", order.getItems().stream()
.mapToInt(Item::getPrice)
.sum()
);
})
)
.build();
Most Common Options for Beginners
Here are the essential options you'll likely need first:
1. defaultNotNull Option - Preventing null Values
The defaultNotNull option ensures that properties not explicitly marked as nullable (with annotations like @Nullable in Java or ? in Kotlin) will not be null. This is useful when you want to avoid null-related issues in your tests.
The Java examples below also set objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE) because the sample Product class uses Lombok @Value (which generates an all-args constructor with @ConstructorProperties). Choose the introspector that matches your class type.
- Java
- Kotlin
// given
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.defaultNotNull(true)
.build();
// when
Product product = fixtureMonkey.giveMeOne(Product.class);
// then
then(product.getProductName()).isNotNull();
then(product.getPrice()).isNotNull();
then(product.getCategory()).isNotNull();
// given
val fixtureMonkey = FixtureMonkey.builder()
.plugin(KotlinPlugin())
.defaultNotNull(true)
.build()
// when
val product: Product = fixtureMonkey.giveMeOne()
// then
then(product.productName).isNotNull
then(product.price).isNotNull
then(product.category).isNotNull
2. javaTypeArbitraryGenerator Option - Controlling Basic Type Generation
The javaTypeArbitraryGenerator option allows you to customize how basic Java types (String, Integer, etc.) are generated. This option is applied through the JqwikPlugin, which integrates Fixture Monkey with the Jqwik property-based testing library.
- Java
- Kotlin
// given
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.plugin(
new JqwikPlugin()
.javaTypeArbitraryGenerator(new JavaTypeArbitraryGenerator() {
@Override
public StringArbitrary strings() {
return Arbitraries.strings().alpha().ofLength(10);
}
})
)
.build();
// when
String generatedString = fixtureMonkey.giveMeOne(String.class);
// then
then(generatedString).hasSize(10);
then(generatedString).matches("[a-zA-Z]+");
// given
val fixtureMonkey = FixtureMonkey.builder()
.plugin(
JqwikPlugin()
.javaTypeArbitraryGenerator(object : JavaTypeArbitraryGenerator {
override fun strings(): StringArbitrary {
return Arbitraries.strings().alpha().ofLength(10)
}
})
)
.build()
// when
val generatedString: String = fixtureMonkey.giveMeOne()
// then
then(generatedString).hasSize(10)
then(generatedString).matches("[a-zA-Z]+")
3. register Option - Setting Type-Specific Default Rules
The register option allows you to configure default settings for specific types. This is useful when you have consistent requirements for a class across multiple tests.
- Java
- Kotlin
// given
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.register(
Product.class,
fm -> fm.giveMeBuilder(Product.class)
.set("price", Arbitraries.longs().greaterOrEqual(1))
.set("category", "Electronics")
)
.build();
// when
Product product = fixtureMonkey.giveMeOne(Product.class);
// then
then(product.getPrice()).isPositive();
then(product.getCategory()).isEqualTo("Electronics");
// given
val fixtureMonkey = FixtureMonkey.builder()
.plugin(KotlinPlugin())
.register(Product::class.java) { builder ->
builder.giveMeBuilder(Product::class.java)
.set("price", Arbitraries.longs().greaterOrEqual(1))
.set("category", "Electronics")
}
.build()
// when
val product: Product = fixtureMonkey.giveMeOne()
// then
then(product.price).isPositive
then(product.category).isEqualTo("Electronics")
4. plugin Option - Adding Extended Functionality
The plugin option allows you to integrate additional features provided by various plugins that Fixture Monkey offers. This option is essential when working with specific frameworks or libraries.
- Java
- Kotlin
@Test
void testPluginOption() {
// Create a FixtureMonkey with Jackson plugin for JSON support
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.plugin(new JacksonPlugin()) // Add support for Jackson annotations
.build();
// Now you can create objects with proper Jackson annotation support
JsonProduct product = fixtureMonkey.giveMeOne(JsonProduct.class);
// Jackson annotations on JsonProduct will be respected
// (e.g., @JsonProperty, @JsonIgnore, etc.)
}
@Test
fun testPluginOption() {
// Create a FixtureMonkey with Jackson plugin for JSON support
val fixtureMonkey = FixtureMonkey.builder()
.plugin(KotlinPlugin()) // Support for Kotlin features
.plugin(JacksonPlugin()) // Add support for Jackson annotations
.build()
// Now you can create objects with proper Jackson annotation support
val product = fixtureMonkey.giveMeOne<JsonProduct>()
// Jackson annotations on JsonProduct will be respected
// (e.g., @JsonProperty, @JsonIgnore, etc.)
}
5. defaultArbitraryContainerInfoGenerator Option - Controlling Container Sizes
The defaultArbitraryContainerInfoGenerator option allows you to control the size of generated container types such as lists, sets, and maps. This option is useful when you need containers of specific sizes in your tests.
- Java
- Kotlin
// given
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultArbitraryContainerInfoGenerator(context -> new ArbitraryContainerInfo(3, 3))
.build();
// when
List<String> stringList = fixtureMonkey.giveMeOne(new TypeReference<List<String>>() {});
Map<Integer, String> map = fixtureMonkey.giveMeOne(new TypeReference<Map<Integer, String>>() {});
// then
then(stringList).hasSize(3);
then(map).hasSize(3);
// given
val fixtureMonkey = FixtureMonkey.builder()
.plugin(KotlinPlugin())
.defaultArbitraryContainerInfoGenerator { ArbitraryContainerInfo(3, 3) }
.build()
// when
val stringList: List<String> = fixtureMonkey.giveMeOne()
// then
then(stringList).hasSize(3)
Which Option to Use When?
Here's a simple guide to help you choose which option to use:
| Option | When to Use |
|---|---|
defaultNotNull(true) | When you want to ensure test objects have no null values (except explicitly nullable properties) |
javaTypeArbitraryGenerator | When you need to customize how basic types like strings or numbers are generated |
register(Class, function) | When you need consistent default values or constraints for a specific class |
plugin(Plugin) | When you need additional features like support for frameworks (Jackson, Kotlin, etc.) |
defaultArbitraryContainerInfoGenerator | When you need to control the size of generated containers (lists, sets, maps, etc.) |
Understanding Option Scope
There are important points to understand when using options:
- Instance Scope
- Options only apply to the FixtureMonkey instance they're configured on
- You can create multiple instances with different settings
// Test settings
FixtureMonkey testFixture = FixtureMonkey.builder()
.defaultNotNull(true)
.build();
// Development settings
FixtureMonkey devFixture = FixtureMonkey.builder()
.defaultNotNull(false)
.build();
- Option Priority
- More specific options take precedence over general ones
- Later options override earlier ones
FixtureMonkey fixture = FixtureMonkey.builder()
.defaultNotNull(true) // All fields non-null
.register(Product.class, fm -> fm.giveMeBuilder(Product.class)
.setNull("description")) // Allow null for description only
.build();
Next Steps
We recommend following this learning path to make the most of Fixture Monkey:
- Start here: Overview (this page) - Understand what options are and how they're structured
- Next step for beginners: Essential Options for Beginners - Learn the most commonly used options for everyday testing needs
- Understand the concepts: Option Concepts - Gain deeper knowledge of how options work internally and learn key terminology
- Advanced features: Advanced Options for Experts - Explore options for complex testing scenarios