Generation

OBJECTO

Objecto is a Java library that helps create random objects for unit tests. It makes it easy to generate complex objects, so developers can focus on testing rather than setting up objects. Objecto offers various annotations for customization, allowing developers to adjust how

Object Generation Steps

StepDescriptionAnnotations

Instantiation

Creating an instance of the object.

Randomization

Randomly generating values for the object's fields.

Modification

Setting values using methods and parameters marked with @Modifier.

Post-Processing

Processing the object after it has been created.

@Constructor

The @Constructor annotation identifies a method for instantiating objects of a particular type.

When Objecto cannot instantiate an object due to the absence of a public constructor, static factory method, or because the type is an abstract class or interface, specific methods become crucial.

Example:

@Constructor
private Attachment<?> newAttachment() {
    return Attachment.builder().fileContent(new byte[0]).build();
}

@Seed

The @Seed annotation sets a specific seed value for random data generation, ensuring reproducibility of the results. It can be applied to methods or types to guarantee that random data generation produces consistent outputs across multiple runs.

Example:

import com.cariochi.objecto.Seed;

@Seed(12345)
Issue createIssue();

@Settings

The @Settings annotation offers a flexible and comprehensive way to define constraints and configurations for various data types. It contains several nested annotations that allow developers to set detailed rules for ranges, sizes, depths, nullability, and more for primitive types, collections, maps, arrays, and strings. This flexibility helps fine-tune random data generation, ensuring consistency while supporting a variety of data types.

For example, @Range annotations for types like Long, Integer, and Double allow users to define minimum (inclusive) and maximum (exclusive) values. Similarly, @Size annotations for collections and maps set constraints on their sizes.

Additionally, the @Datafaker annotation integrates the Datafaker library, enabling developers to specify features like locale and method selection. This ensures generated data can simulate realistic values based on the selected locale or format. The DatafakerMethod class contains a set of predefined methods, allowing developers to generate specific types of random data with ease.

In addition to predefined methods, developers can use any method available in the Datafaker library, as documented in the Datafaker Providers.

For example:

β€’ To use new Faker().boardgame().name(), you can specify @Settings.Datafaker.Method("boardgame().name()") or a simplified version: @Settings.Datafaker.Method("boardgame.name").

β€’ For methods with parameters, such as new Faker().lorem().sentences(3), you can specify: @Settings.Datafaker.Method("lorem().sentences(3)").

Examples:

import com.cariochi.objecto.Settings;
import com.cariochi.objecto.DatafakerMethod;

@Settings.MaxDepth(5)
@Settings.MaxRecursionDepth(5)
@Settings.Nullable(true)
@Settings.Integers.Range(from = 1, to = 10)
@Settings.Longs.Range(from = 1L, to = 10L)
@Settings.Shorts.Range(from = 1, to = 10)
@Settings.Bytes.Range(from = 1, to = 10)
@Settings.Chars.Range(from = 'a', to = 'z')
@Settings.BigDecimals.Range(from = 1.0, to = 10.0)
@Settings.BigDecimals.Scale(2)
@Settings.Doubles.Range(from = 1.0, to = 10.0)
@Settings.Floats.Range(from = 1.0, to = 10.0)
@Settings.Dates.Range(from = "2024-01-01T00:00:00-05:00", to = "2024-01-02T00:00:00-05:00")
@Settings.Collections.Size(5)
@Settings.Collections.Size.Range(from = 1, to = 10)
@Settings.Arrays.Size(5)
@Settings.Arrays.Size.Range(from = 1, to = 10)
@Settings.Maps.Size(5)
@Settings.Maps.Size.Range(from = 1, to = 10)
@Settings.Strings.Length(5)
@Settings.Strings.Length.Range(from = 1, to = 10)
@Settings.Strings.Parameters(letters = true, digits = true, uppercase = false, useFieldNamePrefix = false)
@Settings.Datafaker.Locale("en")
@Settings.Datafaker.Method(DatafakerMethod.TimeAndDate.Past)
interface IssueFactory {
    Issue createIssue();
}

@Fields

The @Fields annotation is designed to configure various constraints and properties for fields in a class. It includes multiple nested annotations that allow developers to define detailed configurations for individual fields, such as specifying value ranges, sizes, nullability, and data generation methods.

β€’ @Fields.SetValue allows the developer to set a specific value for a field.

β€’ @Fields.SetNullexplicitly sets a specified field to null.

β€’ @Fields.Size is used to set the size for fields that represent arrays, collections, maps, or strings.

β€’ @Fields.Range defines the range of values a field can take. For numeric types, it sets the minimum and maximum values. For collections, arrays, maps, and strings, it specifies the range for their size.

β€’ @Fields.Nullable configures whether a specific field can be set to null.

β€’ @Fields.Datafaker allows the use of the Datafaker library to generate field values based on specific methods and locales.

Examples:

import com.cariochi.objecto.Fields;

@Fields.Nullable(field = "description", value = true)
@Fields.SetNull("id")
@Fields.SetValue(field = "status", value = "OPEN")
@Fields.Size(field = "subtasks", value = 5)
@Fields.Range(field = "priority", from = 1, to = 10)
@Fields.Datafaker(field = "creationDate", method = DatafakerMethod.TimeAndDate.Past)
Issue createIssue();

@TypeFactory

The @TypeFactory annotation is used to designate a method as a factory for generating randomized instances of a specific type. When an instance of the specified type is required, this factory method will be invoked to produce the object. This allows for custom control over how objects are created during random data generation.

Parameters:

Factory methods can either have no parameters or accept a single parameter. The accepted parameter types are:

β€’ java.util.Random

β€’ com.cariochi.objecto.utils.ObjectoRandom

During generation, Objecto provides the current Random or ObjectoRandom instance with the configured seed, allowing developers to generate random data in a consistent and repeatable manner.

Examples:

import com.cariochi.objecto.Fields;
import com.cariochi.objecto.ObjectoRandom;
import com.cariochi.objecto.References;
import com.cariochi.objecto.TypeFactory;

// custom type factory implementation
@TypeFactory
default Issue createIssue(ObjectoRandom random) {
    return Issue.builder()
            .key("ID-" + random.nextInt(1000, 9999))
            .name(random.nextString())
            .build();
}

// or abstract type factory
@TypeFactory
@References("subtasks[*].parent")
@Fields.Datafaker(field = "creationDate", method = DatafakerMethod.TimeAndDate.Past)
Issue createIssue();

@FieldFactory

The @FieldFactory annotation is used to define a method that generates values for a specific field of a given type. This provides developers with fine-grained control over how individual field values are generated during the random data generation process.

This annotation supports various scenarios for generating values:

β€’ Simple Field Name: Generate a value for a single field, e.g., "key".

β€’ Nested Fields: Target nested fields using a path like "property.value".

β€’ Array or List Indexing: Use index-based notation to generate values for array or list fields, such as "properties[*].value".

β€’ Method Invocation: Generate values that can be passed as parameters to methods, e.g., "properties.setSize(?)".

Parameters:

Factory methods can either have no parameters or accept a single parameter. The accepted parameter types are:

β€’ java.util.Random

β€’ com.cariochi.objecto.utils.ObjectoRandom

During generation, Objecto provides the current Random or ObjectoRandom instance with the configured seed, allowing developers to generate random data in a consistent and repeatable manner.

Examples:

import com.cariochi.objecto.FieldFactory;

Issue createIssue();

// custom field factories

@FieldFactory(type = Issue.class, field = "key")
private String issueKeyFactory(Random random) {
    return "ID-" + random.nextInt(1000, 10000);
}

@FieldFactory(type = Issue.class, field = "attributes[*].value")
private String generateIssueAttributeValues(ObjectoRandom random) {
    return random.nextDatafakerInstant("lorem.word");
}

// abstract field factory

@FieldFactory(type = Attachment.class, field = "fileName")
@Settings.Datafaker.Method(DatafakerMethod.File.FileName)
String attachmentFileNameFactory();

@References

The @References annotation is used to manage relationships between objects during random object generation. It plays a key role in ensuring proper associations between entities, particularly in the context of bidirectional relationships and complex inter-entity connections. By using @References, developers can instruct Objecto to reuse already generated objects for specific fields rather than creating new instances, ensuring consistency and correctness.

Examples:

public interface IssueFactory {

    // Sets the parent of each subtask to the generated issue
    @References("subtasks[*].parent")
    Issue createIssue(); 
}

public interface CourseFactory {

    // Ensures that the course generated is assigned to both professor assignments and enrollments
    @References({"professor.assignments[*].course", "enrollments[*].course"})
    Course createCourse();

    // Ensures the same student object is referenced in multiple enrollments
    @References("enrollments[*].student")
    Student createStudent();   
}

@PostProcessor

The @PostProcessor annotation is used to define methods that process objects after they have been created during random object generation. This annotation allows developers to perform custom modifications or apply additional logic to the generated objects, ensuring that the final state of the object meets specific requirements or constraints.

Use Cases:

β€’ Apply custom transformations to generated objects after their creation.

β€’ Ensure specific fields are derived or calculated based on other properties of the object.

β€’ Modify objects to conform to specific formatting rules or data consistency requirements.

Example:

// Post-process the generated User object to set username and email
@PostProcessor
private void userPostProcessor(User user) {
    String username = user.getFullName().toLowerCase().replace(".", "").replace(" ", ".");
    user.setUsername(username);
    user.setEmail(username + "@" + new Faker().internet().domainName());
}

Last updated