Customization Options

Fixture Monkey also provides options through the FixtureMonkeyBuilder to customize objects to have the desired values or to use custom property names.

Change how property names are resolved

defaultPropertyNameResolver, pushPropertyNameResolver, pushAssignableTypePropertyNameResolver, pushExactTypePropertyNameResolver

Options related to the PropertyNameResolver allow you to customize how you refer to your properties.

The defaultPropertyNameResolver option is used to change the way property names are figured out for all types. If you want to make specific changes for certain types, you can use pushPropertyNameResolver, pushAssignableTypePropertyNameResolver, or pushExactTypePropertyNameResolver.

By default, a property will be referenced by its original name. Let’s take a look at the following example to see how we can customize the property name:

@Data // getter, setter
public class Product {
    String productName;
}

@Test
void test() {
    // given
    FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
        .pushPropertyNameResolver(MatcherOperator.exactTypeMatchOperator(String.class, (property) -> "string"))
        .build();
    String expected = "test";

    // when
    String actual = fixtureMonkey.giveMeBuilder(Product.class)
        .set("string", expected)
        .sample()
        .getProductName();

    // then
    then(actual).isEqualTo(expected);
}
data class Product (
    val productName: String
)

@Test
fun test() {
    // given
    val fixtureMonkey = FixtureMonkey.builder()
        .plugin(KotlinPlugin())
        .pushPropertyNameResolver(
            MatcherOperator.exactTypeMatchOperator(String::class.java, PropertyNameResolver { "string" })
        )
        .build()
        val expected = "test"

    // when
    val actual: String = fixtureMonkey.giveMeBuilder<Product>()
        .set("string", expected)
        .sample()
        .productName

    // then
    then(actual).isEqualTo(expected)
}

Normally, the property name will resolve to the original property name “productName”. However, with pushPropertyNameResolver the String type properties are now referred to by the name “string”.

Register a default ArbitraryBuilder for a given type

register, registerGroup, registerExactType, registerAssignableType

Sometimes your class may need to consistently match certain constraints. It can be inconvenient and result in lengthy code if you always have to modify the ArbitraryBuilder using customization APIs. In such cases, you can set a default ArbitraryBuilder for a class that will satisfy all the basic constraints.

The register option helps to register an ArbitraryBuilder for a specific type.

For example, the following code demonstrates how to register an ArbitraryBuilder for a Product class. By doing so, all Product instances created by FixtureMonkey will have an id value greater than or equal to “0”.

FixtureMonkey.builder()
    .register(
        Product.class,
        fixture -> fixture.giveMeBuilder(Product.class)
            .set("id", Arbitraries.longs().greaterOrEqual(0))
    )
    .build();
FixtureMonkey.builder()
    .register(Product::class.java) {
        it.giveMeBuilder<Product>()
            .set("id", Arbitraries.longs().greaterOrEqual(0))
    }
    .build()

If you want to register several ArbitraryBuilders at once, you can use the registerGroup option. This can be done using either reflection or the ArbitraryBuilderGroup interface.

Using reflection:

public class GenerateGroup {
    public ArbitraryBuilder<GenerateString> generateString(FixtureMonkey fixtureMonkey) {
        return fixtureMonkey.giveMeBuilder(GenerateString.class)
            .set("value", Arbitraries.strings().numeric());
    }

    public ArbitraryBuilder<GenerateInt> generateInt(FixtureMonkey fixtureMonkey) {
        return fixtureMonkey.giveMeBuilder(GenerateInt.class)
            .set("value", Arbitraries.integers().between(1, 100));
    }
}

FixtureMonkey.builder()
    .registerGroup(GenerateGroup.class)
    .build();
class GenerateGroup {
    fun generateString(fixtureMonkey: FixtureMonkey): ArbitraryBuilder<GenerateString> {
        return fixtureMonkey.giveMeBuilder<GenerateString>()
            .set("value", Arbitraries.strings().numeric())
    }

    fun generateInt(fixtureMonkey: FixtureMonkey): ArbitraryBuilder<GenerateInt> {
        return fixtureMonkey.giveMeBuilder<GenerateInt>()
            .set("value", Arbitraries.integers().between(1, 100))
    }
}

FixtureMonkey.builder()
    .registerGroup(GenerateGroup::class.java)
    .build()

Using ArbitraryBuilderGroup interface:

public class GenerateBuilderGroup implements ArbitraryBuilderGroup {
    @Override
    public ArbitraryBuilderCandidateList generateCandidateList() {
        return ArbitraryBuilderCandidateList.create()
            .add(
                ArbitraryBuilderCandidateFactory.of(GenerateString.class)
                    .builder(
                        arbitraryBuilder -> arbitraryBuilder
                            .set("value", Arbitraries.strings().numeric())
                    )
            )
            .add(
                ArbitraryBuilderCandidateFactory.of(GenerateInt.class)
                    .builder(
                        builder -> builder
                            .set("value", Arbitraries.integers().between(1, 100))
                    )
            );
    }
}

FixtureMonkey.builder()
    .registerGroup(new GenerateBuilderGroup())
    .build();
class GenerateBuilderGroup : ArbitraryBuilderGroup {
    override fun generateCandidateList(): ArbitraryBuilderCandidateList {
        return ArbitraryBuilderCandidateList.create()
            .add(
                ArbitraryBuilderCandidateFactory.of(GenerateString::class.java)
                    .builder { it.set("value", Arbitraries.strings().numeric()) }
            )
            .add(
                ArbitraryBuilderCandidateFactory.of(GenerateInt::class.java)
                    .builder { it.set("value", Arbitraries.integers().between(1, 100)) }
            )
    }
}

FixtureMonkey.builder()
    .registerGroup(GenerateBuilderGroup())
    .build()

Use expression strict mode

useExpressionStrictMode

When using expressions (especially String Expressions), it’s hard to know if the expression you’ve written has a matching property, and the property is correctly adjusted. Using the useExpressionStrictMode option will throw an IllegalArgumentException if the expression you wrote doesn’t have a matching property.

@Test
void test() {
    FixtureMonkey fixtureMonkey = FixtureMonkey.builder().useExpressionStrictMode().build();

    thenThrownBy(
        () -> fixtureMonkey.giveMeBuilder(String.class)
            .set("nonExistentField", 0)
            .sample()
    ).isExactlyInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("No matching results for given NodeResolvers.");
}
@Test
fun test() {
    val fixtureMonkey = FixtureMonkey.builder().useExpressionStrictMode().build()

    assertThatThrownBy {
        fixtureMonkey.giveMeBuilder<String>()
            .set("nonExistentField", 0)
            .sample()
    }.isExactlyInstanceOf(IllegalArgumentException::class.java)
        .hasMessageContaining("No matching results for given NodeResolvers.")
}

Constrain Java types

javaTypeArbitraryGenerator, javaTimeTypeArbitraryGenerator

You can modify the default values for Java primitive types (such as strings, integers, doubles, etc.) by implementing a custom JavaTypeArbitraryGenerator interface. This option can be applied through the JqwikPlugin.

For example, by default, string types generated with Fixture Monkey have unreadable data because it considers edge cases such as when control blocks are contained in strings.

If you prefer to generate strings consisting only of alphabetic characters, you can override the JavaTypeArbitraryGenerator as demonstrated below:

FixtureMonkey.builder()
    .plugin(
        new JqwikPlugin()
              .javaTypeArbitraryGenerator(new JavaTypeArbitraryGenerator() {
                  @Override
                  public StringArbitrary strings() {
                      return Arbitraries.strings().alpha();
                  }
              })
    )
    .build();
FixtureMonkey.builder()
    .plugin(
        JqwikPlugin()
            .javaTypeArbitraryGenerator(object : JavaTypeArbitraryGenerator {
                override fun strings(): StringArbitrary = Arbitraries.strings().alpha()
            })
    )
    .build()

For Java time types, you can use javaTimeTypeArbitraryGenerator.

Constrain Java types with annotations

javaArbitraryResolver, javaTimeArbitraryResolver

Similar to using the javax-validation plugin and adding constraints to your Java typed properties, you can apply constraints to Java types using annotations. To do this, you can implement a JavaArbitraryResolver interface. This option can be applied through the JqwikPlugin.

For example, if you have a custom annotation named MaxLengthOf10, which means that the length of a property should be limited to a maximum of 10 characters, you can create a JavaArbitraryResolver as shown below:

FixtureMonkey.builder()
    .plugin(
        new JqwikPlugin()
            .javaArbitraryResolver(new JavaArbitraryResolver() {
                @Override
                public Arbitrary<String> strings(StringArbitrary stringArbitrary, ArbitraryGeneratorContext context) {
                    if (context.findAnnotation(MaxLengthof10.class).isPresent()) {
                        return stringArbitrary.ofMaxLength(10);
                    }
                    return stringArbitrary;
                }
            })
    )
    .build();
FixtureMonkey.builder()
    .plugin(
        JqwikPlugin()
            .javaArbitraryResolver(object : JavaArbitraryResolver {
                override fun strings(stringArbitrary: StringArbitrary, context: ArbitraryGeneratorContext): Arbitrary<String> {
                    if (context.findAnnotation(MaxLengthof10::class.java).isPresent) {
                        return stringArbitrary.ofMaxLength(10)
                    }
                return stringArbitrary
                }
            })
    )
    .build()

Change Nullability

defaultNotNull

defaultNotNull, nullableContainer, nullableElement

When you want to ensure that the properties of your instance are not null, you can utilize the options mentioned below.

  • defaultNotNull determines whether a null property is generated. If true, property cannot be null
  • nullableContainer determines whether a container property can be null. If true, container can be null
  • nullableElement determines whether an element within a container property can be null. If true, element can be null.

By default, these three options are set to false. You can modify them to true as needed.

FixtureMonkey fixtureMonkey = FixtureMonkey.builder().defaultNotNull(true).build();

FixtureMonkey fixtureMonkey = FixtureMonkey.builder().nullableContainer(true).build();

FixtureMonkey fixtureMonkey = FixtureMonkey.builder().nullableElement(true).build();
val fixtureMonkey = FixtureMonkey.builder().defaultNotNull(true).build()

val fixtureMonkey = FixtureMonkey.builder().nullableContainer(true).build()

val fixtureMonkey = FixtureMonkey.builder().nullableElement(true).build()

NullInjectGenerator

defaultNullInjectGenerator, pushNullInjectGenerator, pushExactTypeNullInjectGenerator, pushAssignableTypeNullInjectGenerator

In cases where a property should be null regardless of any nullable markers, you can make use of the options associated with the NullInjectGenerator.

The defaultnullInjectGenerator option allows you to set the probability of properties being null.

By default, the probability of a property being null is set to 20%.

If you want it to always be null, you can set it to 1.0d. There are predefined values available in the DefaultNullInjectGenerator—NOT_NULL_INJECT(0.0d) and ALWAYS_NULL_INJECT(1.0d)—which you can import and use.

Alternatively, for more customized behavior, you can implement your own NullInjectGenerator.

FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .defaultNullInjectGenerator((context) -> NOT_NULL_INJECT) // you can use NOT_NULL_INJECT or write your probability as 0.4
    .build()
val fixtureMonkey = FixtureMonkey.builder()
    .plugin(KotlinPlugin())
    .defaultNullInjectGenerator { NOT_NULL_INJECT } // you can use NOT_NULL_INJECT or write your probability as 0.4
    .build()

If you want to specifically change the probability of a certain type being null, you can use pushNullInjectGenerator.

FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .pushNullInjectGenerator(MatcherOperator.exactTypeMatchOperator(SimpleObject.class, (context) -> NOT_NULL_INJECT))
    .build();
val fixtureMonkey = FixtureMonkey.builder()
    .pushNullInjectGenerator(
        exactTypeMatchOperator(
            Product::class.java,
            NullInjectGenerator { context -> NOT_NULL_INJECT }
        )
    )
    .build()

Registering an ArbitraryBuilder of a specific class with register that has the .setNotNull("*") setting will have the same effect.