Path expressions
What you will learn in this document
- How to select specific fields or properties of a test object
- How to reference specific parts of an object using string expressions
- How to access properties in various structures like nested objects, arrays, and lists
Introduction to Path Expressions
When writing tests, you often need to modify specific fields of your test objects. Path expressions in Fixture Monkey are like GPS coordinates that help you precisely locate and modify any part of your test object.
As a beginner, think of path expressions as a way to "navigate" through your object structure to reach exactly the field you want to change.
Basic Object Structure Example
To understand path expressions, let's use a simple example object:
- Java
- Kotlin
@Value
public class JavaClass {
String field; // A simple string field
String[] array; // An array of strings
List<String> list; // A list of strings
Nested object; // A nested object
List<Nested> objectList; // A list of nested objects
@Value
public static class Nested {
String nestedField; // A field inside the nested object
}
}
data class KotlinClass(
val field: String,
val array: Array<String>,
val list: List<String>,
val `object`: Nested,
val objectList: List<Nested>
) {
data class Nested(
val nestedField: String
)
}
Visual Map of Path Expressions
Think of your object as a tree structure. Each path expression is like directions to a specific location in that tree:
JavaClass / KotlinClass
│
├── field → "field" // Direct field access
│
├── array → "array" // The entire array
│ ├── array[0] → "array[0]" // First element in array
│ ├── array[1] → "array[1]" // Second element in array
│ └── all elements → "array[*]" // ALL elements in array (wildcard)
│
├── list → "list" // The entire list
│ ├── list[0] → "list[0]" // First element in list
│ ├── list[1] → "list[1]" // Second element in list
│ └── all elements → "list[*]" // ALL elements in list (wildcard)
│
├── object → "object" // The nested object
│ └── nestedField → "object.nestedField" // Field inside nested object
│
└── objectList → "objectList" // List of nested objects
├── objectList[0] → "objectList[0]" // First object in the list
│ └── nestedField → "objectList[0].nestedField" // Field in first object
│
├── objectList[1] → "objectList[1]" // Second object in the list
│ └── nestedField → "objectList[1].nestedField" // Field in second object
│
└── all elements → "objectList[*]" // ALL objects in the list
└── nestedField → "objectList[*].nestedField" // Field in ALL objects
Simple Path Expressions Guide
String path expressions work identically in Java and Kotlin. The examples below show both languages.
1. Selecting the Root Object
To select the entire object itself, use "$":
- Java
- Kotlin
ArbitraryBuilder<JavaClass> builder = fixtureMonkey.giveMeBuilder(JavaClass.class);
builder.set("$", new JavaClass(...));
val builder = fixtureMonkey.giveMeBuilder<KotlinClass>()
builder.set("$", KotlinClass(...))
2. Selecting a Direct Field
- Java
- Kotlin
builder.set("field", "Hello World");
builder.set("field", "Hello World")
3. Selecting a Nested Field
- Java
- Kotlin
builder.set("object.nestedField", "Nested Value");
builder.set("object.nestedField", "Nested Value")
4. Working with Collections
- Java
- Kotlin
// Set the first item in the list
builder.set("list[0]", "First Item");
// Set ALL items in the list (wildcard)
builder.set("list[*]", "Same Value");
// Set the first item in the list
builder.set("list[0]", "First Item")
// Set ALL items in the list (wildcard)
builder.set("list[*]", "Same Value")
5. Working with Arrays
Very similar to lists:
- Java
- Kotlin
builder.set("array[0]", "First Element");
builder.set("array[*]", "All Elements");
builder.set("array[0]", "First Element")
builder.set("array[*]", "All Elements")
6. Complex Nested Paths
You can combine these patterns to go as deep as you need:
- Java
- Kotlin
// nestedField of first object in list
builder.set("objectList[0].nestedField", "First Nested");
// nestedField of ALL objects in list
builder.set("objectList[*].nestedField", "All Nested");
// nestedField of first object in list
builder.set("objectList[0].nestedField", "First Nested")
// nestedField of ALL objects in list
builder.set("objectList[*].nestedField", "All Nested")
Type-Safe Selection
Java: JavaGetter
If you prefer to avoid string-based expressions in Java, you can use type-safe getters:
// Direct field
builder.set(javaGetter(JavaClass::getField), "Hello World");
// Nested field
builder.set(
javaGetter(JavaClass::getObject).into(Nested::getNestedField),
"Nested Value"
);
// Collection elements
builder.set(javaGetter(JavaClass::getList).index(String.class, 0), "First");
builder.set(javaGetter(JavaClass::getList).allIndex(String.class), "All");
Kotlin: DSL Exp
Kotlin users can use property references for type-safe expressions:
// Direct field
builder.setExp(KotlinClass::field, "Hello World")
// Nested field
builder.setExp(KotlinClass::`object` into KotlinClass.Nested::nestedField, "Nested Value")
// Collection elements
builder.setExp(KotlinClass::objectList["0"] into KotlinClass.Nested::nestedField, "First")
builder.setExp(KotlinClass::objectList["*"] into KotlinClass.Nested::nestedField, "All")
For more details on Kotlin DSL Expressions, see the Kotlin DSL Exp page.
Common Beginner Questions
What happens if I try to access an out-of-bounds index?
If you try to access an element that doesn't exist (e.g., "list[5]" when the list only has 3 items), Fixture Monkey will simply ignore that setting. To catch these issues, you can enable Expression Strict Mode.
How do I handle maps?
While you can't directly access map elements with path expressions, you can use InnerSpec to customize maps.
Can I use multiple path expressions at once?
Yes! You can chain multiple .set() calls:
- Java
- Kotlin
ArbitraryBuilder<JavaClass> builder = fixtureMonkey.giveMeBuilder(JavaClass.class)
.set("field", "Value 1")
.set("object.nestedField", "Value 2")
.set("list[*]", "Value 3");
val builder = fixtureMonkey.giveMeBuilder<KotlinClass>()
.set("field", "Value 1")
.set("object.nestedField", "Value 2")
.set("list[*]", "Value 3")
Advanced Options
Expression Strict Mode
Enable this option to make Fixture Monkey validate all path expressions:
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.setExpressionStrictMode(true)
.build();
With strict mode enabled, invalid paths will throw exceptions, helping you catch mistakes early.
Summary
Path expressions are a powerful feature of Fixture Monkey that let you:
- Navigate to any part of your test object structure
- Set specific values for testing different scenarios
- Modify multiple related fields in one operation
- Keep your test code clean and readable
Start with simple direct field access, then gradually explore collection access and nested properties as you grow comfortable with the syntax.