FAQ

How do I get started with Fixture Monkey?

Fixture Monkey provides a simple way to create test objects with random values. Here’s how to get started:

// Create a FixtureMonkey instance
FixtureMonkey fixtureMonkey = FixtureMonkey.create();

// Generate a random object
Person person = fixtureMonkey.giveMeOne(Person.class);
// Create a FixtureMonkey instance
val fixtureMonkey = FixtureMonkey.create()

// Generate a random object
val person = fixtureMonkey.giveMeOne<Person>()

How do I add Fixture Monkey to my project?

You can easily add Fixture Monkey to your Maven or Gradle project:

implementation("com.navercorp.fixturemonkey:fixture-monkey:1.1.x")
implementation 'com.navercorp.fixturemonkey:fixture-monkey:1.1.x'
<dependency>
    <groupId>com.navercorp.fixturemonkey</groupId>
    <artifactId>fixture-monkey</artifactId>
    <version>1.1.x</version>
</dependency>

How do I specify values for certain fields while keeping others random?

You can use the set() method to specify values for specific fields:

Person person = fixtureMonkey.giveMeBuilder(Person.class)
    .set("name", "John Doe")
    .set("age", 25)
    .sample();
val person = fixtureMonkey.giveMeBuilder<Person>()
    .setExpGetter(Person::getName, "John Doe")
    .setExpGetter(Person::getAge, 25)
    .sample()

How do I control the size of collections like List, Set or Map?

You can control the size of collections using the size() method:

Person person = fixtureMonkey.giveMeBuilder(Person.class)
    .size("friends", 5) // Sets the size of the friends list to 5
    .sample();

// Setting a range for size
Product product = fixtureMonkey.giveMeBuilder(Product.class)
    .size("tags", 2, 5) // The tags list will have between 2 and 5 elements
    .sample();
val person = fixtureMonkey.giveMeBuilder<Person>()
    .setExpSize(Person::getFriends, 5) // Sets the size of the friends list to 5
    .sample()

// Setting a range for size
val product = fixtureMonkey.giveMeBuilder<Product>()
    .setExpSize(Product::getTags, 2, 5) // The tags list will have between 2 and 5 elements
    .sample()

How do I handle null values?

You can control null probability using the nullInject option:

// Create a FixtureMonkey with no null values
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .nullInject(0.0) // Set null probability to 0
    .build();

// Create a FixtureMonkey with 50% null probability
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .nullInject(0.5) // Set null probability to 50%
    .build();

// Set a specific field to null
Person person = fixtureMonkey.giveMeBuilder(Person.class)
    .set("address", null)
    .sample();
// Create a FixtureMonkey with no null values
val fixtureMonkey = FixtureMonkey.builder()
    .nullInject(0.0) // Set null probability to 0
    .build()

// Create a FixtureMonkey with 50% null probability
val fixtureMonkey = FixtureMonkey.builder()
    .nullInject(0.5) // Set null probability to 50%
    .build()

// Set a specific field to null
val person = fixtureMonkey.giveMeBuilder<Person>()
    .setExpGetter(Person::getAddress, null)
    .sample()

How do I make my tests reproducible?

You can use a fixed seed to generate the same data across test runs:

// Create a FixtureMonkey with a fixed seed
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .seed(123L)
    .build();
// Create a FixtureMonkey with a fixed seed
val fixtureMonkey = FixtureMonkey.builder()
    .seed(123L)
    .build()

With JUnit, you can also use the @Seed annotation:

@Test
@Seed(123L)
void testWithSeed() {
    Person person = fixtureMonkey.giveMeOne(Person.class);
    // The same Person will be generated every time
}
@Test
@Seed(123L)
fun testWithSeed() {
    val person = fixtureMonkey.giveMeOne<Person>()
    // The same Person will be generated every time
}

How do I ensure generated objects satisfy certain conditions?

You can use setPostCondition() to filter generated objects that don’t meet your criteria:

// Ensure the person is an adult
Person adult = fixtureMonkey.giveMeBuilder(Person.class)
    .setPostCondition(person -> person.getAge() >= 18)
    .sample();

// Ensure a specific field meets a condition
Product product = fixtureMonkey.giveMeBuilder(Product.class)
    .setPostCondition("price", Double.class, price -> price > 0 && price < 1000)
    .sample();
// Ensure the person is an adult
val adult = fixtureMonkey.giveMeBuilder<Person>()
    .setPostCondition { it.age >= 18 }
    .sample()

// Ensure a specific field meets a condition
val product = fixtureMonkey.giveMeBuilder<Product>()
    .setPostConditionExpGetter(Product::getPrice, Double::class.java) { it > 0 && it < 1000 }
    .sample()

How can I exclude certain values from being generated?

You can use set() with a filter to exclude certain values:

Product product = sut.giveMeBuilder(Product.class)
    .set("productType", ArbitraryUtils.toCombinableArbitrary(Arbitraries.of(ProductType)).filter(it -> it != CLOTHING && it != ELECTRONICS))
    .sample();
val product = sut.giveMeBuilder<Product>()
    .setExpGetter(Product::getProductType, ArbitraryUtils.toCombinableArbitrary(Arbitraries.of(ProductType::class.java)).filter { it != ProductType.CLOTHING && it != ProductType.ELECTRONICS })
    .sample()

Or you can use setPostCondition() which works like a filter:

Product product = sut.giveMeBuilder(Product.class)
    .setPostCondition("productType", ProductType.class, it -> it != CLOTHING && it != ELECTRONICS)
    .sample();
val product = sut.giveMeBuilder<Product>()
    .setPostConditionExpGetter(Product::getProductType, ProductType::class.java) { it != ProductType.CLOTHING && it != ProductType.ELECTRONICS }
    .sample()

Please note that using setPostCondition() can incur higher costs for narrow conditions because it filters after the Product instance has been created. In such cases, it’s recommended to use set() instead.

How do I handle nested objects?

Fixture Monkey automatically generates nested objects. You can customize them using a property path:

Order order = fixtureMonkey.giveMeBuilder(Order.class)
    .set("customer.name", "John Doe")
    .set("customer.address.city", "New York")
    .size("items", 3)
    .set("items[0].productName", "Laptop")
    .sample();
val order = fixtureMonkey.giveMeBuilder<Order>()
    .setExp("customer.name", "John Doe")
    .setExp("customer.address.city", "New York")
    .sizeExp("items", 3)
    .setExp("items[0].productName", "Laptop")
    .sample()

One of my fields depends on the value of another field. How can I customize my fixture?

The thenApply() method comes in handy when you need to customize a field that relies on another field:

Money money = fixtureMonkey.giveMeBuilder(Money.class)
    .set("currency", Currency.getInstance("USD"))
    .thenApply((m, builder) -> 
        builder.set("amount", m.getCurrency().equals(Currency.getInstance("USD")) ? 100.0 : 120.0))
    .sample();
val money = fixtureMonkey.giveMeBuilder<Money>()
    .setExpGetter(Money::getCurrency, Currency.getInstance("USD"))
    .thenApply { money, builder -> 
        builder.setExpGetter(Money::getAmount, if (money.currency == Currency.getInstance("USD")) 100.0 else 120.0)
    }
    .sample()

For more information, check the thenApply() section.

How can I limit the range of characters for my generated Strings?

Related - How can I constrain the range of my generated Instant values?

If you want each generated primitive type to adhere to specific constraints, you can use the javaTypeArbitaryGenerator and javaTimeTypeArbitraryGenerator options.

// Configure String generation with specific character ranges
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .javaTypeArbitraryGenerator(new JavaTypeArbitraryGenerator() {
        @Override
        public StringArbitrary strings() {
            return Arbitraries.strings().alpha().ofLength(5, 10);
        }
    })
    .build();

// Configure time generation with specific ranges
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .javaTimeTypeArbitraryGenerator(new JavaTimeTypeArbitraryGenerator() {
        @Override
        public Arbitrary<Instant> instant() {
            Instant start = Instant.parse("2023-01-01T00:00:00Z");
            Instant end = Instant.parse("2023-12-31T23:59:59Z");
            return Arbitraries.instants().between(start, end);
        }
    })
    .build();
// Configure String generation with specific character ranges
val fixtureMonkey = FixtureMonkey.builder()
    .javaTypeArbitraryGenerator(object : JavaTypeArbitraryGenerator() {
        override fun strings(): StringArbitrary {
            return Arbitraries.strings().alpha().ofLength(5, 10)
        }
    })
    .build()

// Configure time generation with specific ranges
val fixtureMonkey = FixtureMonkey.builder()
    .javaTimeTypeArbitraryGenerator(object : JavaTimeTypeArbitraryGenerator() {
        override fun instant(): Arbitrary<Instant> {
            val start = Instant.parse("2023-01-01T00:00:00Z")
            val end = Instant.parse("2023-12-31T23:59:59Z")
            return Arbitraries.instants().between(start, end)
        }
    })
    .build()

Throws an exception when generating a certain type

If you encounter exceptions when generating certain types, try using PriorityConstructorArbitraryIntrospector:

FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .pushExactTypeArbitraryIntrospector(ProblematicType.class, PriorityConstructorArbitraryIntrospector.INSTANCE)
    .build();

// Now generation should work
ProblematicType instance = fixtureMonkey.giveMeOne(ProblematicType.class);
val fixtureMonkey = FixtureMonkey.builder()
    .pushExactTypeArbitraryIntrospector(ProblematicType::class.java, PriorityConstructorArbitraryIntrospector.INSTANCE)
    .build()

// Now generation should work
val instance = fixtureMonkey.giveMeOne<ProblematicType>()

If it does not work, please try to make your own ArbitraryIntrospector or create an issue on GitHub and ask for help.