Generation Options
To create a complex object that matches your desired configurations, Fixture Monkey provides a variety of options for creating objects.
These options are accessible through the FixtureMonkeyBuilder
.
Implement your own Object Generator
ObjectIntrospector
objectIntrospector
An ObjectIntrospector
determines how Fixture Monkey creates objects. The objectIntrospector
option allows you to specify the default behavior when generating an object.
As discussed in the introspector section, you can use predefined introspectors provided by Fixture Monkey or create your own custom introspector.
ArbitraryIntrospector
pushArbitraryIntrospector
,pushAssignableTypeArbitraryIntrospector
,pushExactTypeArbitraryIntrospector
TheArbitraryIntrospector
is responsible for defining how Fixture Monkey chooses the appropriate arbitrary generation strategy and generates an arbitrary. The object is then generated based on the generated arbitrary. You have the flexibility to create a custom introspector by implementing your ownArbitraryIntrospector
.
If you need to change the ArbitraryIntrospector
for a specific type, you can use the above options.
ContainerIntrospector
pushContainerIntrospector
Especially for container types, you can change the ArbitraryIntrospector
using the pushContainerIntrospector
option.
ArbitraryGenerator
defaultArbitraryGenerator
Although the ArbitraryIntrospector
determines the appropriate arbitrary generation strategy, the actual creation of the final arbitrary(CombinableArbitrary
) is done by the ArbitraryGenerator
.
It handles the request by delegating to the ArbitraryIntrospector
.
By using the defaultArbitraryGenerator
option, you have the capability to customize the behavior of the ArbitraryGenerator
.
For instance, you can create an arbitrary generator that produces unique values, as shown in the example below:
public static class UniqueArbitraryGenerator implements ArbitraryGenerator {
private static final Set<Object> UNIQUE = new HashSet<>();
private final ArbitraryGenerator delegate;
public UniqueArbitraryGenerator(ArbitraryGenerator delegate) {
this.delegate = delegate;
}
@Override
public CombinableArbitrary generate(ArbitraryGeneratorContext context) {
return delegate.generate(context)
.filter(
obj -> {
if (!UNIQUE.contains(obj)) {
UNIQUE.add(obj);
return true;
}
return false;
}
);
}
}
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultArbitraryGenerator(UniqueArbitraryGenerator::new)
.build();
class UniqueArbitraryGenerator(private val delegate: ArbitraryGenerator) : ArbitraryGenerator {
companion object {
private val UNIQUE = HashSet<Any>()
}
override fun generate(context: ArbitraryGeneratorContext): CombinableArbitrary {
return delegate.generate(context)
.filter { obj ->
if (!UNIQUE.contains(obj)) {
UNIQUE.add(obj)
true
} else {
false
}
}
}
}
val fixtureMonkey = FixtureMonkey.builder()
.defaultArbitraryGenerator { UniqueArbitraryGenerator(it) }
.build()
Implement your own Property
PropertyGenerator
defaultPropertyGenerator
,pushPropertyGenerator
,pushAssignableTypePropertyGenerator
,pushExactTypePropertyGenerator
PropertyGenerator
creates child properties of the given ObjectProperty
.
The child property can be a field, JavaBeans property, method or constructor parameter within the parent ObjectProperty
.
There are scenarios where you might want to customize how these child properties are generated.
The PropertyGenerator
options allow you to specify how child properties of each type are generated.
This option is mainly used when you want to exclude generating some properties when the parent property has abnormal child properties.
ObjectPropertyGenerator
defaultObjectPropertyGenerator
,pushObjectPropertyGenerator
,pushAssignableTypeObjectPropertyGenerator
,pushExactTypeObjectPropertyGenerator
ObjectPropertyGenerator
generates the ObjectProperty
based on a given context.
With options related to ObjectPropertyGenerator
you can customize how the ObjectProperty
is generated.
ContainerPropertyGenerator
pushContainerPropertyGenerator
,pushAssignableTypeContainerPropertyGenerator
,pushExactTypeContainerPropertyGenerator
The ContainerPropertyGenerator
determines how to generate ContainerProperty
within a given context.
With options related to ContainerPropertyGenerator
you can customize how the ContainerProperty
is generated.
Exclude Classes or Packages from Generation
pushExceptGenerateType
,addExceptGenerateClass
,addExceptGenerateClasses
,addExceptGeneratePackage
,addExceptGeneratePackages
If you want to exclude the generation of certain types or packages, you can use these options.
@Test
void testExcludeClass() {
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.addExceptGenerateClass(String.class)
.build();
String actual = sut.giveMeOne(Product.class)
.getProductName();
then(actual).isNull();
}
@Test
void testExcludePackage() {
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.addExceptGeneratePackage("java.lang")
.build();
String actual = sut.giveMeOne(String.class);
then(actual).isNull();
}
@Test
fun testExcludeClass() {
val fixtureMonkey = FixtureMonkey.builder()
.addExceptGenerateClass(String::class.java)
.build()
val actual = fixtureMonkey.giveMeOne<Product>()
.productName
then(actual).isNull()
}
@Test
fun testExcludePackage() {
val fixtureMonkey = FixtureMonkey.builder()
.addExceptGeneratePackage("java.lang")
.build()
val actual = fixtureMonkey.giveMeOne<String>()
then(actual).isNull()
}
Modify Containers
Container Size
defaultArbitraryContainerInfoGenerator
,pushArbitraryContainerInfoGenerator
ArbitraryContainerInfo
holds information about the minimum and maximum sizes of a Container type.
You can change the behavior by modifying the ArbitraryContainerInfoGenerator
using related options.
The following example demonstrates how to customize ArbitraryContainerInfo
to set the size of all container types to 3.
@Test
void test() {
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultArbitraryContainerInfoGenerator(context -> new ArbitraryContainerInfo(3, 3))
.build();
List<String> actual = fixtureMonkey.giveMeOne();
then(actual).hasSize(3);
}
@Test
fun test() {
val fixtureMonkey = FixtureMonkey.builder()
.defaultArbitraryContainerInfoGenerator { context -> ArbitraryContainerInfo(3, 3) }
.build()
val actual: List<String> = fixtureMonkey.giveMeOne()
then(actual).hasSize(3)
}
Adding Container type
addContainerType
You can add a new custom Container type using the addContainerType
option.
Let’s say you made a new custom Pair class in Java.
You can use this container type by implementing a custom ContainerPropertyGenerator
, Introspector
and DecomposedContainerValueFactory
.
FixtureMonkey fixtureMonkey=FixtureMonkey.builder()
.addContainerType(
Pair.class,
new PairContainerPropertyGenerator(),
new PairIntrospector(),
new PairDecomposedContainerValueFactory()
)
.build();
custom Introspector:
public class PairIntrospector implements ArbitraryIntrospector, Matcher {
private static final Matcher MATCHER = new AssignableTypeMatcher(Pair.class);
@Override
public boolean match(Property property) {
return MATCHER.match(property);
}
@Override
public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) {
ArbitraryProperty property = context.getArbitraryProperty();
ArbitraryContainerInfo containerInfo = property.getContainerProperty().getContainerInfo();
if (containerInfo == null) {
return ArbitraryIntrospectorResult.EMPTY;
}
List<Arbitrary<?>> childrenArbitraries = context.getChildrenArbitraryContexts().getArbitraries();
BuilderCombinator<List<Object>> builderCombinator = Builders.withBuilder(ArrayList::new);
for (Arbitrary<?> childArbitrary : childrenArbitraries) {
builderCombinator = builderCombinator.use(childArbitrary).in((list, element) -> {
list.add(element);
return list;
});
}
return new ArbitraryIntrospectorResult(
builderCombinator.build(it -> new Pair<>(it.get(0), it.get(1)))
);
}
}
custom ContainerPropertyGenerator
:
public class PairContainerPropertyGenerator implements ContainerPropertyGenerator {
@Override
public ContainerProperty generate(ContainerPropertyGeneratorContext context) {
com.navercorp.fixturemonkey.api.property.Property property = context.getProperty();
List<AnnotatedType> elementTypes = Types.getGenericsTypes(property.getAnnotatedType());
if (elementTypes.size() != 2) {
throw new IllegalArgumentException(
"Pair elementsTypes must be have 1 generics type for element. "
+ "propertyType: " + property.getType()
+ ", elementTypes: " + elementTypes
);
}
AnnotatedType firstElementType = elementTypes.get(0);
AnnotatedType secondElementType = elementTypes.get(1);
List<com.navercorp.fixturemonkey.api.property.Property> elementProperties = new ArrayList<>();
elementProperties.add(
new ElementProperty(
property,
firstElementType,
0,
0
)
);
elementProperties.add(
new ElementProperty(
property,
secondElementType,
1,
1
)
);
return new ContainerProperty(
elementProperties,
new ArbitraryContainerInfo(1, 1, false)
);
}
}
custom DecomposedContainerValueFactory
:
public class PairDecomposedContainerValueFactory implements DecomposedContainerValueFactory {
@Override
public DecomposedContainerValue from(Object object) {
Pair<?, ?> pair = (Pair<?, ?>)obj;
List<Object> list = new ArrayList<>();
list.add(pair.getFirst());
list.add(pair.getSecond());
return new DecomposableContainerValue(list, 2);
}
}
Customize Arbitrary Validation
arbitraryValidator
The arbitraryValidator
option allows you to replace the default arbitraryValidator
with your own custom arbitrary validator.
When an instance is sampled
, the arbitraryValidator
validates the arbitrary, and if it is invalid, it throws an exception.
This process is repeated 1,000 times, and if the instance is still invalid, a TooManyFilterMissesException
would be thrown.
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.arbitraryValidator(obj -> {
throw new ValidationFailedException("thrown by custom ArbitraryValidator", new HashSet<>());
})
.build();
thenThrownBy(() -> fixtureMonkey.giveMeOne(String.class))
.isExactlyInstanceOf(FilterMissException.class);
val fixtureMonkey = FixtureMonkey.builder()
.arbitraryValidator { obj ->
throw ValidationFailedException("thrown by custom ArbitraryValidator", HashSet())
}
.build()
assertThatThrownBy { fixtureMonkey.giveMeOne<String>() }
.isExactlyInstanceOf(FilterMissException::class.java)
Modify Arbitrary Generation Retry Limits
generateMaxTries
,generateUniqueMaxTries
The generateMaxTries
option allows you to control the maximum number of attempts to generate a valid object from an arbitrary.
If an object cannot be generated successfully after exceeding this limit (default is 1,000 attempts), a TooManyFilterMissesException
will be thrown.
Additionally, Fixture Monkey ensures the generation of unique values for map keys and set elements.
The generateUniqueMaxTries
option allows you to specify the maximum number of attempts (also defaults to 1,000) that will be made to generate this unique value.
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.generateMaxTries(100)
.generateUniqueMaxTries(100)
.build();
val fixtureMonkey = FixtureMonkey.builder()
.generateMaxTries(100)
.generateUniqueMaxTries(100)
.build()
Implement Interfaces
interfaceImplements
interfaceImplements
is an option used to specify the available implementations for an interface.
When you don’t specify this option, an ArbitraryBuilder for an interface will always result in a null value when sampled. However, when you do specify this option, Fixture Monkey will randomly generate one of the specified implementations whenever an ArbitraryBuilder for the interface is sampled.
interface FixedValue {
Object get();
}
class IntegerFixedValue implements FixedValue {
@Override
public Object get() {
return 1;
}
}
class StringFixedValue implements FixedValue {
@Override
public Object get() {
return "fixed";
}
}
class GenericFixedValue<T> {
T value;
}
@Test
void sampleGenericInterface() {
// given
List<Class<? extends FixedValue>> implementations = new ArrayList<>();
implementations.add(IntegerFixedValue.class);
implementations.add(StringFixedValue.class);
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.interfaceImplements(FixedValue.class, implementations)
.build();
// when
Object actual = fixtureMonkey.giveMeBuilder(new TypeReference<GenericGetFixedValue<FixedValue>>() {})
.setNotNull("value")
.sample()
.getValue()
.get();
// then
then(actual).isIn(1, "fixed");
}
interface FixedValue {
fun get(): Any
}
class IntegerFixedValue : FixedValue {
override fun get(): Any {
return 1
}
}
class StringFixedValue : FixedValue {
override fun get(): Any {
return "fixed"
}
}
class GenericFixedValue<T> {
val value: T
}
@Test
fun sampleGenericInterface() {
// given
val implementations: MutableList<Class<out FixedValue>> = List.of(IntegerFixedValue::class.java, StringFixedValue::class.java)
val fixtureMonkey = FixtureMonkey.builder()
.interfaceImplements(FixedValue::class.java, implementations)
.build()
// when
val actual = fixtureMonkey.giveMeBuilder<GenericGetFixedValue<FixedValue>>()
.sample()
.getValue()
.get()
// then
then(actual).isIn(1, "fixed")
}