The CUI Test Generator framework provides robust and reproducible test data generation. It combines random data generation with the ability to reproduce specific test scenarios, making it ideal for both thorough testing and precise debugging.
<dependency>
<groupId>de.cuioss.test</groupId>
<artifactId>cui-test-generator</artifactId>
</dependency>
The Generators
class is your primary entry point for test data generation. It provides factory methods for creating generators for most Java built-in types:
// Basic type generation
String text = Generators.strings().next();
Integer number = Generators.integers().next();
LocalDateTime dateTime = Generators.localDateTimes().next();
// Configurable generation
String letters = Generators.letterStrings(5, 10).next(); // 5-10 characters
List<Integer> numbers = Generators.integers(1, 100).list(5); // 5 numbers between 1-100
// Fixed and enum values
var urlGen = Generators.fixedValues(String.class,
"https://cuioss.de",
"https://www.heise.de");
var enumGen = Generators.enumValues(TimeUnit.class);
The @EnableGeneratorController
annotation enables reproducible test data generation:
@EnableGeneratorController
class MyGeneratorTest {
@Test
void shouldGenerateConsistentData() {
var result = Generators.strings().next();
assertFalse(result.isEmpty());
}
}
Use @GeneratorSeed
to fix the random seed for reproducible tests:
@EnableGeneratorController
class MyTest {
@Test
@GeneratorSeed(4711L) // Method-level seed
void shouldGenerateSpecificData() {
// Always generates the same data
var data = Generators.strings().next();
}
}
@EnableGeneratorController
@GeneratorSeed(8042L) // Class-level seed
class AllTestsReproducible {
// All tests use the same seed
}
The recommended way to use generators in parameterized tests is with @GeneratorsSource
and the GeneratorType
enum:
@EnableGeneratorController
class GeneratorsSourceTest {
// String generator with size parameters
@ParameterizedTest
@GeneratorsSource(
generator = GeneratorType.STRINGS,
minSize = 3,
maxSize = 10,
count = 5
)
void testWithStringGenerator(String value) {
assertNotNull(value);
assertTrue(value.length() >= 3 && value.length() <= 10);
}
// Number generator with range parameters
@ParameterizedTest
@GeneratorsSource(
generator = GeneratorType.INTEGERS,
low = "1",
high = "100",
count = 5
)
void testWithIntegerGenerator(Integer value) {
assertNotNull(value);
assertTrue(value >= 1 && value <= 100);
}
// Simple generator without parameters
@ParameterizedTest
@GeneratorsSource(
generator = GeneratorType.NON_EMPTY_STRINGS,
count = 3
)
void testWithNonEmptyStrings(String value) {
assertNotNull(value);
assertFalse(value.isEmpty());
}
// Domain-specific generator
@ParameterizedTest
@GeneratorsSource(
generator = GeneratorType.DOMAIN_EMAIL,
count = 3
)
void testWithEmailGenerator(String email) {
assertNotNull(email);
assertTrue(email.contains("@"));
}
// Using GeneratorSeed for reproducible tests
@ParameterizedTest
@GeneratorSeed(42L)
@GeneratorsSource(
generator = GeneratorType.STRINGS,
minSize = 3,
maxSize = 10,
count = 3
)
void testWithSpecificSeed(String value) {
// This test will always generate the same values
assertNotNull(value);
}
}
You can also directly use TypeGenerator in parameterized tests with @TypeGeneratorSource
and @TypeGeneratorMethodSource
:
@EnableGeneratorController
class ParameterizedGeneratorTest {
// Class-based configuration - uses the generator's default constructor
@ParameterizedTest
@TypeGeneratorSource(NonBlankStringGenerator.class)
void testWithGeneratedStrings(String value) {
assertNotNull(value);
assertFalse(value.isBlank());
}
// Generate multiple values
@ParameterizedTest
@TypeGeneratorSource(value = IntegerGenerator.class, count = 5)
void testWithMultipleIntegers(Integer value) {
assertNotNull(value);
}
// Method-based configuration - uses a method that returns a configured generator
@ParameterizedTest
@TypeGeneratorMethodSource("createStringGenerator")
void testWithCustomGenerator(String value) {
assertNotNull(value);
}
// Factory method that returns a configured generator
static TypedGenerator<String> createStringGenerator() {
return Generators.strings(5, 10); // Strings between 5-10 characters
}
// Reference a method in another class
@ParameterizedTest
@TypeGeneratorMethodSource("de.cuioss.test.MyGeneratorFactory#createGenerator")
void testWithExternalGenerator(MyType value) {
// test with value
}
}
Use @TypeGeneratorFactorySource
to create generators using factory methods:
@EnableGeneratorController
class FactoryBasedGeneratorTest {
// Use a factory method to create a generator
@ParameterizedTest
@TypeGeneratorFactorySource(
factoryClass = MyGeneratorFactory.class,
factoryMethod = "createStringGenerator"
)
void testWithFactoryGenerator(String value) {
assertNotNull(value);
}
// Factory with parameters
@ParameterizedTest
@TypeGeneratorFactorySource(
factoryClass = MyGeneratorFactory.class,
factoryMethod = "createRangeGenerator",
methodParameters = {"1", "100"},
count = 5
)
void testWithParameterizedFactory(Integer value) {
assertNotNull(value);
assertTrue(value >= 1 && value <= 100);
}
// With specific seed for reproducible tests
@ParameterizedTest
@GeneratorSeed(42L)
@TypeGeneratorFactorySource(
factoryClass = MyGeneratorFactory.class,
factoryMethod = "createStringGenerator",
count = 3
)
void testWithSpecificSeed(String value) {
// This test will always generate the same values
assertNotNull(value);
}
}
// Factory class
public class MyGeneratorFactory {
public static TypedGenerator<String> createStringGenerator() {
return Generators.strings(5, 10);
}
public static TypedGenerator<Integer> createRangeGenerator(String min, String max) {
return Generators.integers(Integer.parseInt(min), Integer.parseInt(max));
}
}
Use @CompositeTypeGeneratorSource
to combine multiple generators. The preferred approach is to use the generators
attribute with GeneratorType
enum values:
@EnableGeneratorController
class CompositeGeneratorTest {
// Preferred: Combine multiple generators using GeneratorType enum
@ParameterizedTest
@CompositeTypeGeneratorSource(
generators = {
GeneratorType.NON_EMPTY_STRINGS,
GeneratorType.INTEGERS
},
count = 3
)
void testWithGeneratorTypes(String text, Integer number) {
assertNotNull(text);
assertNotNull(number);
}
// Domain-specific generators can also be used
@ParameterizedTest
@CompositeTypeGeneratorSource(
generators = {
GeneratorType.DOMAIN_EMAIL,
GeneratorType.DOMAIN_ZIP_CODE
},
count = 2
)
void testWithDomainGenerators(String email, String zipCode) {
assertNotNull(email);
assertTrue(email.contains("@"));
assertNotNull(zipCode);
}
// Alternative: Combine multiple generator classes
@ParameterizedTest
@CompositeTypeGeneratorSource(
generatorClasses = {
NonBlankStringGenerator.class,
IntegerGenerator.class
},
count = 3
)
void testWithMultipleGenerators(String text, Integer number) {
assertNotNull(text);
assertNotNull(number);
}
// Alternative: Combine multiple generator methods
@ParameterizedTest
@CompositeTypeGeneratorSource(
generatorMethods = {
"createStringGenerator",
"createIntegerGenerator"
},
count = 2
)
void testWithMultipleMethodGenerators(String text, Integer number) {
assertNotNull(text);
assertNotNull(number);
}
// With specific seed for reproducible tests
@ParameterizedTest
@GeneratorSeed(42L)
@CompositeTypeGeneratorSource(
generators = {
GeneratorType.NON_EMPTY_STRINGS,
GeneratorType.INTEGERS
},
count = 2
)
void testWithSpecificSeed(String text, Integer number) {
// This test will always generate the same combinations
assertNotNull(text);
assertNotNull(number);
}
// Generator methods
static TypedGenerator<String> createStringGenerator() {
return Generators.strings(5, 10);
}
static TypedGenerator<Integer> createIntegerGenerator() {
return Generators.integers(1, 100);
}
}
TypedGenerator
is the foundation interface for all generators:
public class CustomGenerator implements TypedGenerator<MyType> {
@Override
public MyType next() {
// Generate and return a new instance
return new MyType(Generators.strings().next());
}
@Override
public Class<MyType> getType() {
return MyType.class;
}
}
The GeneratorType
enum provides a type-safe way to reference all available generators:
-
Standard generators from Generators class:
-
GeneratorType.STRINGS
- Strings generator -
GeneratorType.INTEGERS
- Integers generator -
GeneratorType.BOOLEANS
- Booleans generator -
GeneratorType.LOCAL_DATE_TIMES
- LocalDateTime generator -
GeneratorType.URLS
- URL generator
-
-
Domain-specific generators with DOMAIN_ prefix:
-
GeneratorType.DOMAIN_EMAIL
- Email address generator -
GeneratorType.DOMAIN_CITY
- City name generator -
GeneratorType.DOMAIN_FULL_NAME
- Person name generator -
GeneratorType.DOMAIN_ZIP_CODE
- Zip/postal code generator
-
Each enum value contains:
-
The method name in the Generators class (for standard generators)
-
The factory class (Generators.class or the domain-specific generator class)
-
The return type of the generator
This makes it easy to use with @GeneratorsSource
and @CompositeTypeGeneratorSource
.
The framework provides specialized generators for common domains:
// Collection generation
var stringList = new CollectionGenerator<>(Generators.strings())
.list(5); // List of 5 strings
// Date/Time with zones
var dateTime = new ZonedDateTimeGenerator().future();
// Numeric ranges
var floats = new FloatObjectGenerator(0.0f, 100.0f).next();
// URLs and strings
var url = new URLGenerator().next();
var nonBlank = new NonBlankStringGenerator().next();
// Domain-specific generators (also available via GeneratorType enum)
var email = new EmailGenerator().next();
var city = new CityGenerator().next();
var name = new FullNameGenerator().next();
The package de.cuioss.test.generator.internal.net.java.quickcheck
contains internal implementation details derived from QuickCheck. Do not use any classes from this package directly. Instead, always use the public API through:
-
de.cuioss.test.generator.Generators
-
de.cuioss.test.generator.TypedGenerator
-
Classes in
de.cuioss.test.generator.domain
andde.cuioss.test.generator.impl
-
Use
Generators
as your primary entry point -
Enable
@EnableGeneratorController
for reproducible tests -
Document seeds used for specific test scenarios
-
Create custom generators by implementing
TypedGenerator
-
Use domain-specific generators for specialized test data
-
Never use classes from the internal package