Features
The Interface Plugin is a powerful tool that enables Fixture Monkey to dynamically handle implementations of interfaces and abstract classes during object generation. It is particularly useful when you need to specify concrete implementations for interfaces or abstract classes in your test fixtures.
Abstract
- Register concrete implementations for interfaces
- Register concrete implementations for abstract classes
- Anonymous arbitrary introspector usage option (default: enabled)
- Support for dynamic implementation resolution based on property characteristics using CandidateConcretePropertyResolver
Basic Usage
FixtureMonkey sut = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.interfaceImplements(MyInterface.class, Arrays.asList(MyInterfaceImpl1.class, MyInterfaceImpl2.class))
.abstractClassExtends(MyAbstractClass.class, Arrays.asList(MyConcreteClass1.class, MyConcreteClass2.class))
)
.build();
Disabling Anonymous Arbitrary Introspector
FixtureMonkey sut = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.interfaceImplements(MyInterface.class, Arrays.asList(MyInterfaceImpl.class))
.useAnonymousArbitraryIntrospector(false)
)
.build();
API Reference
interfaceImplements
Registers implementations for a given interface.
public <T> InterfacePlugin interfaceImplements(
Class<T> interfaceType,
List<Class<? extends T>> implementations
)
Parameters:
interfaceType
: The interface class to be implementedimplementations
: List of classes implementing the interface
abstractClassExtends
Registers implementations for a given abstract class.
public <T> InterfacePlugin abstractClassExtends(
Class<T> abstractClassType,
List<Class<? extends T>> implementations
)
Parameters:
abstractClassType
: The abstract class type to be implementedimplementations
: List of classes implementing the abstract class
useAnonymousArbitraryIntrospector
Configures the use of an anonymous arbitrary introspector. By default, this option is enabled (default value: true).
When enabled, it uses an instance of AnonymousArbitraryIntrospector
as the fallback introspector.
public InterfacePlugin useAnonymousArbitraryIntrospector(boolean useAnonymousArbitraryIntrospector)
Parameters:
useAnonymousArbitraryIntrospector
: Whether to use the anonymous arbitrary introspector (default: true)
Example:
// Default behavior (anonymous introspector enabled)
FixtureMonkey sut = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.interfaceImplements(MyInterface.class, Arrays.asList(MyInterfaceImpl.class))
.useAnonymousArbitraryIntrospector(true)
)
.build();
// Anonymous introspector disabled
FixtureMonkey sut2 = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.interfaceImplements(MyInterface.class, Arrays.asList(MyInterfaceImpl.class))
.useAnonymousArbitraryIntrospector(false)
)
.build();
Examples
Basic Interface Implementation
interface Animal {
String sound();
}
class Dog implements Animal {
@Override
public String sound() {
return "Woof";
}
}
class Cat implements Animal {
@Override
public String sound() {
return "Meow";
}
}
FixtureMonkey sut = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.interfaceImplements(Animal.class, Arrays.asList(Dog.class, Cat.class))
)
.build();
// Returns either a Dog or Cat instance
Animal animal = sut.giveMeOne(Animal.class);
Abstract Class Implementation
abstract class Vehicle {
abstract int getWheels();
}
class Car extends Vehicle {
@Override
int getWheels() {
return 4;
}
}
class Bike extends Vehicle {
@Override
int getWheels() {
return 2;
}
}
FixtureMonkey sut = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.abstractClassExtends(Vehicle.class, Arrays.asList(Car.class, Bike.class))
)
.build();
// Returns either a Car or Bike instance
Vehicle vehicle = sut.giveMeOne(Vehicle.class);
Anonymous Object Generation
When useAnonymousArbitraryIntrospector
is enabled, you can generate anonymous implementations for interfaces that don’t have registered implementations. The plugin uses JDK Dynamic Proxy to create these implementations, allowing you to customize the generated values. Here’s an example:
interface UserService {
String getUserName();
int getUserAge();
List<String> getUserRoles();
// Default method - behavior varies by JDK version
default String getFullInfo() {
return getUserName() + " (" + getUserAge() + ")";
}
}
FixtureMonkey sut = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.useAnonymousArbitraryIntrospector(true)
)
.build();
// Generates an anonymous implementation with random values using JDK Dynamic Proxy
UserService anonymousUserService = sut.giveMeOne(UserService.class);
// Customizes the generated values
UserService customAnonymousUserService = sut.giveMeBuilder(UserService.class)
.set("userName", "John Doe")
.set("userAge", 30)
.set("userRoles", Arrays.asList("ADMIN", "USER"))
.sample();
The generated anonymous implementation:
- Is created using JDK Dynamic Proxy
- Returns random values for all interface methods by default
- Can be customized using
giveMeBuilder
- Maintains consistent values across multiple method calls
- Supports all primitive types, objects, and collections
Note: Default method behavior varies by JDK version:
- In JDK 17, default methods maintain their original implementation
- In JDK versions prior to 17, default methods are proxied and return random values
- This means the original implementation is ignored, and default methods return random values like other methods
- For example, the
getFullInfo()
method will return a random string instead of using its original implementation
Advanced Usage
Using Custom Matchers
FixtureMonkey sut = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.interfaceImplements(
new ExactTypeMatcher(MyInterface.class),
Arrays.asList(MyInterfaceImpl1.class, MyInterfaceImpl2.class)
)
)
.build();
Using CandidateConcretePropertyResolver
CandidateConcretePropertyResolver
provides a flexible way to dynamically determine concrete implementations for interfaces or abstract classes. It can make decisions at runtime based on various factors:
- Property name
- Property type
- Property annotations
- Property metadata
- Other property characteristics
The resolver’s resolve
method is called for each property that requires a concrete implementation, enabling you to:
- Return a single implementation for specific cases
- Return multiple implementations for random selection
- Return different implementations based on property characteristics
- Apply complex business logic to determine the appropriate implementation
Here’s an example:
interface Animal {
String sound();
String getName();
}
class Dog implements Animal {
@Override
public String sound() {
return "Woof";
}
@Override
public String getName() {
return "Dog";
}
}
class Cat implements Animal {
@Override
public String sound() {
return "Meow";
}
@Override
public String getName() {
return "Cat";
}
}
// Custom resolver that returns implementations only when the property name is "animal"
class AnimalResolver implements CandidateConcretePropertyResolver {
@Override
public List<Class<?>> resolve(Property property) {
if ("animal".equals(property.getName())) {
return Arrays.asList(Dog.class, Cat.class);
}
return Collections.emptyList();
}
}
FixtureMonkey sut = FixtureMonkey.builder()
.plugin(new InterfacePlugin()
.interfaceImplements(
new ExactTypeMatcher(Animal.class),
new AnimalResolver()
)
)
.build();
// Randomly selects either Dog or Cat when the property name is "animal"
Animal animal = sut.giveMeOne(Animal.class);