Skip to main content
Version: v1.1.x

Generating Interface Types

Why Generate Interface Types?

When writing tests, you often need to work with interfaces rather than concrete implementations:

  • You may be testing code that accepts interfaces as parameters
  • Your system under test may return interface types
  • You want to test behavior without coupling to specific implementations

Fixture Monkey makes it easy to generate test objects for interfaces - whether they're simple interfaces, generic interfaces, or sealed interfaces.

Quick Start Example

Here's a simple example to get started with interface generation:

note

Anonymous interface generation requires the InterfacePlugin with useAnonymousArbitraryIntrospector(true) enabled.

// given
FixtureMonkey fixture = anonymousFixture();

// when
StringSupplier supplier = fixture.giveMeOne(StringSupplier.class);

// then
then(supplier.getValue()).isNotNull();

This example generates an anonymous implementation of the StringSupplier interface that you can use in your tests. Let's explore more options for interface generation.

Interface Generation Approaches

Fixture Monkey provides three main approaches for generating interface instances:

ApproachDescriptionBest For
Anonymous implementationFixture Monkey creates an anonymous classQuick tests, simple interfaces
Specific implementationYou specify which class to useMore control, realistic behavior
Built-in implementationsFixture Monkey provides defaults for common interfacesStandard Java interfaces

Examples for Each Approach

// Anonymous implementation
StringSupplier supplier = fixture.giveMeOne(StringSupplier.class);

// Specific implementation
InterfacePlugin plugin = new InterfacePlugin()
.interfaceImplements(StringSupplier.class, List.of(DefaultStringSupplier.class));

// Built-in implementation
List<String> list = fixture.giveMeOne(new TypeReference<List<String>>() {});

Common Interface Types with Built-in Support

Fixture Monkey provides default implementations for common Java interfaces:

  • ListArrayList
  • SetHashSet
  • MapHashMap
  • QueueLinkedList
  • And more...

You don't need to configure anything special to use these.

Detailed Examples

Simple Interface

Let's start with a simple interface example:

class GeneratingInterfaceTest {

public interface StringSupplier {
String getValue();
}

public static class DefaultStringSupplier implements StringSupplier {
private final String value;

@ConstructorProperties("value")
public DefaultStringSupplier(String value) {
this.value = value;
}

@Override
public String getValue() {
return "default" + value;
}
}

public interface ObjectValueSupplier<T> {
T getValue();
}

public static class StringValueSupplier implements ObjectValueSupplier<String> {
private final String value;

@ConstructorProperties("value")
public StringValueSupplier(String value) {
this.value = value;
}

@Override
public String getValue() {
return value;
}
}

public static class IntegerValueSupplier implements ObjectValueSupplier<Integer> {
private final Integer value;

@ConstructorProperties("value")
public IntegerValueSupplier(Integer value) {
this.value = value;
}

@Override
public Integer getValue() {
return value;
}
}

private static FixtureMonkey anonymousFixture() {
return FixtureMonkey.builder()
.defaultNotNull(true)
.plugin(
new InterfacePlugin()
.useAnonymousArbitraryIntrospector(true)
)
.build();
}

@Test
void quickStart() {
// given
FixtureMonkey fixture = anonymousFixture();

// when
StringSupplier supplier = fixture.giveMeOne(StringSupplier.class);

// then
then(supplier.getValue()).isNotNull();
}

@Test
void testWithAnonymousImplementation() {
// given
FixtureMonkey fixture = anonymousFixture();

// when
StringSupplier result = fixture.giveMeOne(StringSupplier.class);

// then
then(result.getValue()).isNotNull();
then(result).isNotInstanceOf(DefaultStringSupplier.class);
}

@Test
void testWithCustomizedProperties() {
// given
FixtureMonkey fixture = anonymousFixture();

// when
StringSupplier result = fixture.giveMeBuilder(StringSupplier.class)
.set("value", "customValue")
.sample();

// then
then(result.getValue()).isEqualTo("customValue");
}

@Test
void testWithSpecificImplementation() {
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.plugin(
new InterfacePlugin()
.interfaceImplements(StringSupplier.class, Arrays.asList(DefaultStringSupplier.class))
)
.build();

// when
StringSupplier result = fixture.giveMeOne(StringSupplier.class);

// then
then(result).isInstanceOf(DefaultStringSupplier.class);
then(result.getValue()).startsWith("default");
}

@Test
void testGenericInterfaceWithoutTypeParameters() {
// given
FixtureMonkey fixture = anonymousFixture();

// when
ObjectValueSupplier<?> result = fixture.giveMeOne(ObjectValueSupplier.class);

// then
then(result.getValue()).isNotNull();
}

@Test
void testGenericInterfaceWithTypeParameters() {
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.defaultNotNull(true)
.plugin(
new InterfacePlugin()
.interfaceImplements(ObjectValueSupplier.class,
Arrays.asList(IntegerValueSupplier.class))
)
.build();

// when
ObjectValueSupplier<Integer> result =
fixture.giveMeOne(new TypeReference<ObjectValueSupplier<Integer>>() {});

// then
then(result.getValue()).isInstanceOf(Integer.class);
}

@Test
void testGenericInterfaceWithSpecificImplementation() {
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.defaultNotNull(true)
.plugin(
new InterfacePlugin()
.interfaceImplements(ObjectValueSupplier.class,
Arrays.asList(StringValueSupplier.class, IntegerValueSupplier.class))
)
.build();

// when
ObjectValueSupplier<?> result = fixture.giveMeOne(ObjectValueSupplier.class);

// then
then(result).isNotNull();
then(result.getValue()).isNotNull();
}

@Test
void testCustomListImplementation() {
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.plugin(
new InterfacePlugin()
.interfaceImplements(List.class, Arrays.asList(LinkedList.class))
)
.build();

// when
List<String> list = fixture.giveMeOne(new TypeReference<List<String>>() {});

// then
then(list).satisfiesAnyOf(
actual -> then(actual).isInstanceOf(ArrayList.class),
actual -> then(actual).isInstanceOf(LinkedList.class)
);
}

@Test
void testInterfaceHierarchy() {
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.plugin(
new InterfacePlugin()
.interfaceImplements(Collection.class, Arrays.asList(List.class))
)
.build();

// when
Collection<String> collection = fixture.giveMeOne(new TypeReference<Collection<String>>() {});

// then
then(collection).isInstanceOf(List.class);
}
}

Approach 1: Anonymous Implementation (No Options)

The simplest approach is to let Fixture Monkey generate an anonymous implementation:

JavatestWithAnonymousImplementation()
GeneratingInterfaceTest.java
// given
FixtureMonkey fixture = anonymousFixture();

// when
StringSupplier result = fixture.giveMeOne(StringSupplier.class);

// then
then(result.getValue()).isNotNull();
then(result).isNotInstanceOf(DefaultStringSupplier.class);

With this approach, Fixture Monkey creates an anonymous object that implements the StringSupplier interface. The getValue() method returns a randomly generated String.

Important

Fixture Monkey only generates property values for methods that:

  • Follow the naming convention of getters (like getValue(), getName(), etc.)
  • Have no parameters

Other methods will always return null or default primitive values.

You can customize the generated properties using the same API as for regular classes:

JavatestWithCustomizedProperties()
GeneratingInterfaceTest.java
// given
FixtureMonkey fixture = anonymousFixture();

// when
StringSupplier result = fixture.giveMeBuilder(StringSupplier.class)
.set("value", "customValue")
.sample();

// then
then(result.getValue()).isEqualTo("customValue");

Approach 2: Using a Specific Implementation

When you need more realistic behavior, you can tell Fixture Monkey to use your concrete implementation:

JavatestWithSpecificImplementation()
GeneratingInterfaceTest.java
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.plugin(
new InterfacePlugin()
.interfaceImplements(StringSupplier.class, Arrays.asList(DefaultStringSupplier.class))
)
.build();

// when
StringSupplier result = fixture.giveMeOne(StringSupplier.class);

// then
then(result).isInstanceOf(DefaultStringSupplier.class);
then(result.getValue()).startsWith("default");

This approach generates a real DefaultStringSupplier instance with the behavior defined in your implementation.

Generic Interface

For generic interfaces, the approach varies depending on whether you specify type parameters:

1. Without Type Parameters

When you create a generic interface without specifying type parameters, Fixture Monkey defaults to using String type:

JavatestGenericInterfaceWithoutTypeParameters()
GeneratingInterfaceTest.java
// given
FixtureMonkey fixture = anonymousFixture();

// when
ObjectValueSupplier<?> result = fixture.giveMeOne(ObjectValueSupplier.class);

// then
then(result.getValue()).isNotNull();

2. With Explicit Type Parameters

You can specify the type parameter using TypeReference along with a registered implementation:

JavatestGenericInterfaceWithTypeParameters()
GeneratingInterfaceTest.java
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.defaultNotNull(true)
.plugin(
new InterfacePlugin()
.interfaceImplements(ObjectValueSupplier.class,
Arrays.asList(IntegerValueSupplier.class))
)
.build();

// when
ObjectValueSupplier<Integer> result =
fixture.giveMeOne(new TypeReference<ObjectValueSupplier<Integer>>() {});

// then
then(result.getValue()).isInstanceOf(Integer.class);

3. Using a Specific Implementation

When using a specific implementation, it follows the type parameters of that implementation:

JavatestGenericInterfaceWithSpecificImplementation()
GeneratingInterfaceTest.java
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.defaultNotNull(true)
.plugin(
new InterfacePlugin()
.interfaceImplements(ObjectValueSupplier.class,
Arrays.asList(StringValueSupplier.class, IntegerValueSupplier.class))
)
.build();

// when
ObjectValueSupplier<?> result = fixture.giveMeOne(ObjectValueSupplier.class);

// then
then(result).isNotNull();
then(result.getValue()).isNotNull();
Note

When generating a generic interface without type parameters, Fixture Monkey uses String as the default type. If you need a different type, use TypeReference or specify a concrete implementation.

Sealed Interface (Java 17+)

Java 17 introduced sealed interfaces, which explicitly define their permitted implementations. Fixture Monkey automatically handles these without additional configuration:

// Sealed interface with permitted implementations
sealed interface SealedStringSupplier {
String getValue();
}

// Permitted implementation
public static final class SealedDefaultStringSupplier implements SealedStringSupplier {
private final String value;

@ConstructorProperties("value")
public SealedDefaultStringSupplier(String value) {
this.value = value;
}

@Override
public String getValue() {
return "sealed" + value;
}
}

@Test
void testSealedInterface() {
// Setup
FixtureMonkey fixture = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.build();

// Generate sealed interface
SealedStringSupplier result = fixture.giveMeOne(SealedStringSupplier.class);

// Test
assertThat(result).isInstanceOf(SealedDefaultStringSupplier.class);
assertThat(result.getValue()).startsWith("sealed");
}

Combining with Other Interfaces

You can also specify which implementation to use for certain interfaces. For example, adding LinkedList as an implementation for List:

JavatestCustomListImplementation()
GeneratingInterfaceTest.java
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.plugin(
new InterfacePlugin()
.interfaceImplements(List.class, Arrays.asList(LinkedList.class))
)
.build();

// when
List<String> list = fixture.giveMeOne(new TypeReference<List<String>>() {});

// then
then(list).satisfiesAnyOf(
actual -> then(actual).isInstanceOf(ArrayList.class),
actual -> then(actual).isInstanceOf(LinkedList.class)
);
note

When you add a custom implementation using interfaceImplements, the default built-in implementation (e.g., ArrayList for List) may also remain as a candidate. Fixture Monkey can randomly select from all available candidates, including both the default and custom implementations.

Interface Inheritance

Fixture Monkey can also handle interface inheritance. You can specify implementations at any level of the hierarchy:

JavatestInterfaceHierarchy()
GeneratingInterfaceTest.java
// given
FixtureMonkey fixture = FixtureMonkey.builder()
.plugin(
new InterfacePlugin()
.interfaceImplements(Collection.class, Arrays.asList(List.class))
)
.build();

// when
Collection<String> collection = fixture.giveMeOne(new TypeReference<Collection<String>>() {});

// then
then(collection).isInstanceOf(List.class);

Advanced Features

For more complex scenarios, Fixture Monkey provides advanced options for interface implementation resolution.

Dynamic Implementation Resolution

If you have many implementations or need to select implementations based on type conditions:

FixtureMonkey fixture = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.plugin(
new InterfacePlugin()
.interfaceImplements(
new AssignableTypeMatcher(ObjectValueSupplier.class),
property -> {
Class<?> actualType = Types.getActualType(property.getType());
if (StringValueSupplier.class.isAssignableFrom(actualType)) {
return List.of(PropertyUtils.toProperty(DefaultStringValueSupplier.class));
}

if (IntegerValueSupplier.class.isAssignableFrom(actualType)) {
return List.of(PropertyUtils.toProperty(DefaultIntegerValueSupplier.class));
}
return List.of();
}
)
)
.build();
For Advanced Users

This section describes advanced features that most beginners won't need initially. Feel free to revisit this when you need more complex interface generation strategies.

Custom Resolution Implementation

For the most advanced scenarios, you can implement the CandidateConcretePropertyResolver interface:

class YourCustomCandidateConcretePropertyResolver implements CandidateConcretePropertyResolver {
@Override
public List<Property> resolveCandidateConcreteProperties(Property property) {
// Your custom logic to resolve implementations
return List.of(...);
}
}

You can use the built-in ConcreteTypeCandidateConcretePropertyResolver to help with type conversion:

FixtureMonkey fixture = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.interfaceImplements(
new ExactTypeMatcher(Collection.class),
new ConcreteTypeCandidateConcretePropertyResolver<>(List.of(List.class, Set.class))
)
)
.build();
Important

When setting type conditions for option application, be careful with matchers like AssignableTypeMatcher. Using it incorrectly can cause infinite recursion if implementations also match the condition.

Summary

Here's a quick summary of how to generate interface types with Fixture Monkey:

  1. Simple cases: Just use fixture.giveMeOne(YourInterface.class) to get an anonymous implementation

  2. Specific implementation: Use the InterfacePlugin with interfaceImplements:

    new InterfacePlugin().interfaceImplements(YourInterface.class, List.of(YourImplementation.class))
  3. Built-in implementations: Common interfaces like List, Set, etc. are handled automatically

  4. Sealed interfaces: No special configuration needed - Fixture Monkey uses the permitted implementations

  5. Complex cases: Use AssignableTypeMatcher or implement CandidateConcretePropertyResolver for advanced scenarios

Remember that for most testing scenarios, the simpler approaches will be sufficient. The advanced features are there when you need more control over the generated implementations.