Fixture Customization APIs
Fixture Monkey offers a range of APIs within the ArbitraryBuilder class that enable customization of objects created by it.
Customizing Fixtures
set()
The set() method is used to assign values to one or more properties referenced by the expression.
Different types, including Supplier, Arbitrary, ArbitraryBuilder, NOT_NULL, NULL, or Just can be used as the value.
Additionally, a certain instance of an object can also be used as the value.
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.set("id", 1000);
fixtureMonkey.giveMeBuilder<Product>()
.setExp(Product::id, 1000)
Just
Using an instance wrapped by
Justwhen usingset()makes you set the value directly instead of decomposing. Normally, when youset()a property inArbitraryBuilderit does not use an instance of the given value, it does a deep copy instead. So, if you need to set with an instance, you can useValues.just(instance)This feature can be useful in cases where you need to set a property to a mock instance when using a mocking framework.
Note that you cannot set a child property after setting with
Just.
Product product = fixture.giveMeBuilder(Product.class)
.set("options", Values.just(List.of("red", "medium", "adult"))
.set("options[0]", "blue")
.sample();
For example, the product instance created above, will not have the value "blue" for the first element of options. It will remain the list given with
Just.
size(), minSize(), maxSize()
The size() method lets you specify the size of container properties.
You have the flexibility to either set a precise size or specify a range using the minimum and maximum values.
Alternatively, you can use minSize() or maxSize() to set only the minimum or maximum container size.
(By default, the size range is from 0 to 3 elements.)
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.size("options", 5); // size:5
fixtureMonkey.giveMeBuilder(Product.class)
.size("options", 3, 5); // minSize:3, maxSize:5
fixtureMonkey.giveMeBuilder(Product.class)
.minSize("options", 3); // minSize:3
fixtureMonkey.giveMeBuilder(Product.class)
.maxSize("options", 5); // maxSize:5
fixtureMonkey.giveMeBuilder<Product>()
.sizeExp(Product::options, 5) // size:5
fixtureMonkey.giveMeBuilder<Product>()
.sizeExp(Product::options, 3, 5) // minSize:3, maxSize:5
fixtureMonkey.giveMeBuilder<Product>()
.minSizeExp(Product::options, 3) // minSize:3
fixtureMonkey.giveMeBuilder<Product>()
.maxSizeExp(Product::options, 5) // maxSize:5
setNull(), setNotNull()
At times, you might want to ensure that a property is either always set to null or always has a value.
In such situations, you can use setNull() or setNotNull().
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.setNull("id");
fixtureMonkey.giveMeBuilder(Product.class)
.setNotNull("id");
fixtureMonkey.giveMeBuilder<Product>()
.setNullExp(Product::id)
fixtureMonkey.giveMeBuilder<Product>()
.setNotNullExp(Product::id)
setInner()
With setInner() you can apply customizations defined within an InnerSpec instance to your builder.
An InnerSpec is a type-independent specification for the customizations to be applied.
Instances of InnerSpec can be reused to consistently and easily configure nested properties.
This feature is particularly beneficial when customizing map properties.
For additional guidance, refer to InnerSpec
- Java
- Kotlin
InnerSpec innerSpec = new InnerSpec()
.property("merchantInfo", it -> it.entry(1000, "ABC Store"));
fixtureMonkey.giveMeBuilder(Product.class)
.setInner(innerSpec)
val innerSpec = InnerSpec()
.property("merchantInfo") { it.entry(1000, "ABC Store") }
fixtureMonkey.giveMeBuilder(Product.class)
.setInner(innerSpec)
setLazy()
The setLazy() function assigns the property a value obtained from the provided Supplier.
The Supplier will run every time the ArbitraryBuilder is sampled.
This can be particularly useful when you need to generate unique sequential IDs or set the most recent value.
- Java
- Kotlin
AtomicReference<Long> variable = new AtomicReference<>(0L);
ArbitraryBuilder<Long> builder = fixtureMonkey.giveMeBuilder(Long.class)
.setLazy("$", () -> variable.getAndSet(variable.get() + 1));
Long actual1 = builder.sample(); // actual1 == 0
Long actual2 = builder.sample(); // actual2 == 1
var variable = 0L
val builder = fixtureMonkey.giveMeBuilder(Long::class.java)
.setLazy("$") { variable++ }
val actual1 = builder.sample() // actual1 == 0
val actual2 = builder.sample() // actual2 == 1
setPostCondition()
setPostCondition() can be used when your fixture needs to adhere to a specific condition.
This condition can be defined by passing a predicate.
Using setPostCondition can incur higher costs for narrow conditions. In such cases, it's recommended to use set instead.
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.setPostCondition("id", Long.class, it -> it > 0)
fixtureMonkey.giveMeBuilder(Product::class.java)
.setPostConditionExp(Product::id, Long::class.java) { it: Long -> it > 0 }
fixed()
fixed() can be used when you want your arbitrary builder to consistently return instances with the same values every time it is sampled.
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.fixed()
fixtureMonkey.giveMeBuilder<Product>()
.fixed()
limit
For the set(), setLazy(), and setPostCondition() methods, you can include an additional parameter that determines the number of times the customization will be applied.
This can be advantageous when the expression refers to multiple properties.
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.set("options[*]", "red", 2); // up to 2 elements in options will be set to "red"
fixtureMonkey.giveMeBuilder<Product>()
.set("options[*]", "red", 2) // up to 2 elements in options will be set to "red"
Expanding Customization using Sampled Results
thenApply()
The thenApply() method becomes handy when you need to customize a field based on the sampled result of the builder.
For instance, let's assume you want the "productName" field to match the generated "id" of the Product.
You can use thenApply() as follows:
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.thenApply((it, builder) -> builder.set("productName", it.getId().toString()))
fixtureMonkey.giveMeBuilder(Product::class.java)
.thenApply{it, builder -> builder.setExp(Product::productName, it.id.toString())}
acceptIf()
You might also find the need to perform additional customization based on a specific condition.
In such cases, you can utilize the acceptIf() method, which applies the customization only when the predicate is satisfied.
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.acceptIf(
it -> it.getProductType() == ProductType.CLOTHING,
builder -> builder.set("price", 1000)
)
fixtureMonkey.giveMeBuilder<Product>()
.acceptIf(
{ it.productType == ProductType.CLOTHING },
{ builder -> builder.setExp(Product::price, 1000) }
)
Transforming the Type of ArbitraryBuilder
map()
The map() function is used to convert the ArbitraryBuilder type into another type.
- Java
- Kotlin
fixtureMonkey.giveMeBuilder(Product.class)
.map(Product::getId); // transforms to ArbitraryBuilder<Long>
fixtureMonkey.giveMeBuilder(Product::class.java)
.map(Product::id) // transforms to ArbitraryBuilder<Long>
zipWith()
zipWith() becomes useful when you want to merge multiple ArbitraryBuilders to create an ArbitraryBuilder of a different type.
You have to define how you intend to combine the builders.
- Java
- Kotlin
ArbitraryBuilder<String> stringBuilder = fixtureMonkey.giveMeBuilder(String.class);
ArbitraryBuilder<String> zipped = fixtureMonkey.giveMeBuilder(Integer.class)
.zipWith(stringBuilder, (integer, string) -> integer + "" + string);
val stringBuilder = fixtureMonkey.giveMeBuilder<String>()
val zipped = fixtureMonkey.giveMeBuilder<Int>()
.zipWith(stringBuilder) { int, string -> int.toString() + "" + string }