Git Product home page Git Product logo

instancio / instancio Goto Github PK

View Code? Open in Web Editor NEW
750.0 9.0 44.0 8.46 MB

A library that creates fully populated objects for your unit tests.

Home Page: https://www.instancio.org

License: Apache License 2.0

Java 99.47% Shell 0.01% HTML 0.15% CSS 0.01% Kotlin 0.36% Just 0.02%
java testing data-generator test-automation unit-testing random random-generation test-data-generator junit junit-jupiter

instancio's Introduction

Instancio Maven Central License Quality Gate Status Coverage


What is it?

Instancio is a Java library that automatically creates and populates objects for your unit tests.

Instead of manually setting up test data:

Address address  = new Address();
address.setStreet("street");
address.setCity("city");
//...

Person person = new Person();
person.setFirstName("first-name");
person.setLastName("last-name");
person.setAge(22);
person.setGender(Gender.MALE);
person.setAddress(address);
//...

You can simply do the following:

Person person = Instancio.create(Person.class);

This one-liner returns a fully-populated person, including nested objects and collections.

The object is populated with random data that can be reproduced in case of test failure.

What else can Instancio do?

  1. Create collections of objects:
List<Person> persons = Instancio.ofList(Person.class).size(10).create();
  1. Create streams of objects:
Stream<Person> persons = Instancio.stream(Person.class).limit(5);
  1. Create generic types:
Pair<List<Foo>, List<Bar>> pairOfLists = Instancio.create(new TypeToken<Pair<List<Foo>, List<Bar>>>() {});
  1. Customise generated values:
Person person = Instancio.of(Person.class)
    .generate(field(Person::getDateOfBirth), gen -> gen.temporal().localDate().past())
    .generate(field(Phone::getAreaCode), gen -> gen.oneOf("604", "778"))
    .generate(field(Phone::getNumber), gen -> gen.text().pattern("#d#d#d-#d#d-#d#d"))
    .subtype(all(AbstractAddress.class), AddressImpl.class)
    .supply(all(LocalDateTime.class), () -> LocalDateTime.now())
    .onComplete(all(Person.class), (Person p) -> p.setName(p.getGender() == Gender.MALE ? "John" : "Jane"))
    .create();
  1. Create reusable templates (Models) of objects:
Model<Person> simpsons = Instancio.of(Person.class)
    .set(field(Person::getLastName), "Simpson")
    .set(field(Address::getCity), "Springfield")
    .generate(field(Person::getAge), gen -> gen.ints().range(40, 50))
    .toModel();

Person homer = Instancio.of(simpsons)
    .set(field(Person::getFirstName), "Homer")
    .create();

Person marge = Instancio.of(simpsons)
    .set(field(Person::getFirstName), "Marge")
    .create();

Main Features

  • Fully reproducible data in case of test failures.
  • Support for generics, record and sealed classes, Java 21 sequenced collections.
  • Support for defining custom generators.
  • Support for generating data based on Bean Validation annotations.
  • Flexible configuration options.
  • InstancioExtension for Junit 5 @ExtendWith.
  • Support for Guava via instancio-guava module (experimental)
  • Support for property-based testing via instancio-quickcheck module (experimental)

Getting Started

  • User guide (instancio.org)

  • Javadocs (javadoc.io)

  • Quickstart (github.com) sample Maven project with usage examples

    git clone https://github.com/instancio/instancio-quickstart.git

Maven Coordinates

If you have JUnit 5 on the classpath, use the instancio-junit dependency.

<dependency>
    <groupId>org.instancio</groupId>
    <artifactId>instancio-junit</artifactId>
    <version>LATEST</version>
    <scope>test</scope>
</dependency>

To use Instancio with JUnit 4, TestNG, or standalone, use instancio-core:

<dependency>
    <groupId>org.instancio</groupId>
    <artifactId>instancio-core</artifactId>
    <version>LATEST</version>
    <scope>test</scope>
</dependency>

Feedback

Feedback and bug reports are greatly appreciated!

  • Please submit an issue for bug reports and feature requests.
  • For general feedback or questions, please create a post in the Discussions.

Please check the User Guide, previous issues and discussions before creating a new one.

Third-party Extensions

Instancio-jpa is an extension on top of the Instancio library that enables the creation and population of JPA entities including the subsequent persistence of these entities using JPA for the purpose of test data generation in integration tests.

Sponsors

If you like Instancio, please consider supporting its maintenance and development by becoming a sponsor.

A big thank you to the current project sponsors:

Thanks to JetBrains and YourKit for supporting this project with their open source licenses.

JetBrains logo

YourKit logo

instancio's People

Contributors

0xflotus avatar armandino avatar dependabot[bot] avatar evaristegalois11 avatar grubeninspekteur avatar nawrok avatar nedis-ua avatar nok3zy avatar patrickdronk avatar reta avatar stevenpg avatar varunu28 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

instancio's Issues

Bad performance when using Instancio for Entities in a data model

Describe the bug
We have a somewhat big data model, that has references between all the classes. I'm trying to create an object, that has a single reference to another object. This reference field is being set to a custom value using the set method on InstancioOfClassAPI. I suspect that instancio creates a builder, that also is able to build this reference class, which also has references to other classes. If this is the case, then this ends up in Instancio making a builder for the whole data model (+- 30 classes). This ends up in the Instancio.of(<myclass>) call taking up to around 1 second, making it not usable for my tests. (Also tried this on a DTO without references to other classes, and here it ran within a few milliseconds as expected.)

Expected result
Ideally, if a field is ignored or set, the api won't create a builder that includes this field, so that performance can be improved a lot.

Actual result
Seems like now, Instancio always creates a builder that is possible to populate all the fields, making it slow on "bigger" data models.

Additional information
Add any other context about the problem here

Sample code reproducing the issue

Instancio.of<myclass>.class).set(field("myfieldToOtherClass"), customCreatedInstance).create()
  • Instancio version:
    2.6.0

Support OffsetDateTime generation

Feature description

Currently it seems like OffsetDateTime isn't supported, unlike ZoneDateTime etc.

We use OffsetDateTime in our services, so it would be nice to have that supported too.

Look into adding more convenient API methods for creating collections

Currently the syntax for creating collections is a little verbose:

List<Person> persons = Instancio.stream(Person.class)
        .limit(5)
        .collect(Collectors.toList());

Investigate the alternatives:

Instancio.createList(Person.class, 10)
Instancio.ofList(Person.class).size(10).create()
Instancio.of(Person.class).createList(10)
Instancio.create(Person.class).list(10)

Instancio.createMap(String.class, Person.class, 10)
Instancio.ofMap(String.class, Person.class).size(10).create()
Instancio.create(String.class, Person.class).map(10)

Any plan to add Jakarta Validation and Hibernate Validator support?

Jakarta Validation and Hibernate Validator have lots of annotations of constraints to validate data, and almost I add annotations for fields in class.

public class User {
    @Positive
    private Long id;
    @UUID
    private String uuid;
    @NotNull
    private String name;
    @Email
    private String email;
    @URL
    private String avatar;
    @Size(min = 6, max = 16)
    private String password;
    private String salt;
    @Size(min = 6, max = 16)
    private String phone;
    private Date createdAt;
}

Could Instancio generates object by validation annotations?

Add generator for `ZoneId`

Currently ZoneId is not populated. Implement generator for creating random values.

assertThat(Instancio.create(ZoneId.class)).isNotNull();

Add support for populating only `null` fields on objects returned by custom generators

Currently Instancio does not modify objects returned by user-supplied generators. This is controlled by the GeneratedHints.ignoreChildren flag which is set to true by default. If the custom generator sets it to false, then Instancio will populate objects returned by custom generators. However this will include fields that were already populated by the user within the generator, which defeats the purpose of using a custom generator in the first place.

Update
The boolean flag ignoreChildren will be replaced with an enum PopulateAction. This should provide enough flexibility for most use cases. The enum values are:

  • NONE - object will not be modified by the engine
  • APPLY_SELECTORS - like NONE except object will be modified only if there are matching supply() or generate() selectors present
  • NULLS - like APPLY_SELECTORS + engine will also populate null fields
  • NULLS_AND_DEFAULT_PRIMITIVES - like NULLS + also populate primitives with default values (0, false, '\u0000')
  • ALL - populate everything; engine will populate all fields, even those with non-null values

The action only affects the behaviour of supply() and generate(). onComplete() callbacks are to be called regardless of the action hint since we cannot assume what logic is performed within the callback.

Difference between records and classes

Hi!

I'm using instancio version 2.6.0 with java 17, and experiencing differences between records and classes when there is an exception in the constructor.

  1. Classes

I have an immutable class as follows:

final class Clazz {
    private final String property1;

    Clazz(final String property1) {
        if (property1 == null) {
            throw new RuntimeException("Should not be null");
        }
        this.property1 = property1;
    }

    public String property1() {
        return property1;
    }
}

And following test that passes:

    @Test
    void passes_and_parameter_is_null() {
        var cr = Instancio.of(Clazz.class)
                .ignore(field(Clazz::property1))
                .create();

        assertThat(cr).isNotNull();// passes. I understand that values are passed directly to the fields.
        assertThat(cr.property1()).isNull();
    }
  1. Records

Next, I have a record with the same logic:

record Recordz(String property1) {
    Recordz {
        if (property1 == null) {
            throw new RuntimeException("Should not be null");
        }
    }
}

The same test fails, but not with exception:

    @Test
    void fails_because_instance_is_null() {
        var result = Instancio.of(Recordz.class)
                .ignore(field(Recordz::property1))
                .create();

        assertThat(result).isNotNull();// fails, instance is null, without any exception
        assertThat(result.property1()).isNull();
    }

Add support to select the root object

Currently, to target the root object, its class must be specified explicitly:

Instancio.of(new TypeToken<Map<String, Person>>() {})
    .set(all(Map.class), gen -> gen.map().size(3))
    .create();

This has two drawbacks

  • it's more verbose than it needs to be.
  • if Person class also contains a Map within it, the above selector will apply to it as well.

Introducing Select.root() would solve both of these issues:

Instancio.of(new TypeToken<Map<String, Person>>() {})
    .set(root(), gen -> gen.map().size(3))
    .create();

Investigate setting values via methods instead of assigning directly to `Field`s

Currently Instancio assigns values directly to fields. In certain cases it may be preferable to set values via setters.

Looking through Easy Random issues, this request came up a few times:

Also related, when checking whether a field is initialised, it may be preferable to do it via a getter:

Initial thoughts

  • Keep the current behaviour of setting fields as the default.
  • Add a configuration setting to populate using methods.
  • Method name resolution should be flexible to deal with different naming conventions, e.g. setFoo(), withFoo(), and foo().
  • Nice to have: configurable behaviour for exceptional cases when
    • a setter cannot be resolved or does not exist
    • method invocation fails (e.g. argument mismatch, validation exception)

Options for exceptional cases:

  • ignore the error and leave the field assigned to its default value
  • fallback to populating directly via the field
  • propagate the error up

API shorthand for creating simple value types

Currently, creating simple value types, such as dates, strings, numbers, etc, requires some boilerplate.

// There is redundancy in specifying the class twice.
LocalDate date = Instancio.of(LocalDate.class)
    .generate(all(LocalDate.class), gen -> gen.temporal().localDate().past())
    .create();

Explore the possibility of adding a convenience method to the API for generating simple value types, e.g.

LocalDate date = Instancio.create(LocalDate.class, gen -> gen.temporal().localDate().past());

// OR

// Since the type can be inferred from the generator,
// it should be possible to omit specifying the class altogether
LocalDate date = Instancio.create(gen -> gen.temporal().localDate().past());

This could also lead to equivalent Stream methods, e.g.

Stream<LocalDate> stream = Instancio.stream(gen -> gen.temporal().localDate().past());

Limitations/drawbacks:

  • will only work for types supported out of the box through the gen parameter
  • will not support collection generators gen.collection() and gen.map()
  • may require changes to the generate() method signature to support hiding collection/map methods
  • will not support specifying a custom seed

Collections from Models

Feature description

Instances supports generation of collections, which is a nicely crafted easy API. But this API only supports Classes and no Model<>. For seems like a break in the API since the actual Class and respected Model are handled interchangeable in the API.

class Person {
  String name;
  Integer age;
  // constructors and getters omitted
}

// test code as desired 
var youngPersonMode = Instancio.of(Person.class)./* set age to 16 */.toModel();

var youngsters = Instancio.ofList(youngPersonModel);

Error creating an enum without values()

enum Foo{}

@Test
void generatingEmptyEnumShouldReturnNull() {
    assertThat(Instancio.create(Foo.class)).isNull();
}

Expected: null
Actual: InstancioException: Error getting enum values for class ...

Add support for excluding certain Enum values + making them nullable

Update the built-in Enum generator to support exclude() and nullable() methods:

User user = Instancio.of(User.class)
    .generate(field("status"), gen -> gen.enumOf(Status.class)
            .exclude(Status.INACTIVE, Status.DISABLED) // exclude given statuses
            .nullable()) // generated enum value can be null
    .create();

Specifying `subtype()` does not work with a generic array

Given an interface and implementation:

interface ItemInterface<T> {
    T getValue();
}

class Item<K> implements ItemInterface<K> {
    private K value;

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

It should be possible to specify the subtype as in the following test (note: should add assertion for array element type):

@SuppressWarnings(Sonar.DISABLED_TEST)
void shouldCreateArrayOfSpecifiedType2() {
final TwoArraysOfItemInterfaceString result = Instancio.of(TwoArraysOfItemInterfaceString.class)
.generate(all(ItemInterface[].class), gen -> gen.array().subtype(Item[].class))
.create();
assertThat(result.getArray1()).isNotEmpty().allSatisfy(it -> {
assertThat(it).isNotNull();
assertThat(it.getValue()).isNotBlank();
});
assertThat(result.getArray2()).isNotEmpty().allSatisfy(it -> {
assertThat(it).isNotNull();
assertThat(it.getValue()).isNotBlank();
});
}

The test is currently disabled as this isn't working as expected.

Expected
Item[] should be generated for ItemInterface[]

Add support for setting maximum depth when generating cyclic objects

Background

Currently, generating large objects containing several cyclic one-to-many collections suffers from poor performance, e.g.

class Container {
    List<ChildA> childrenA;
    List<ChildB> childrenB;
    //...
    List<ChildN> childrenN;
    // ...other fields
}

// where each `Child*` points back to the collection owner
class Child* {
    Container owner;
    // ...other fields
}

Instancio generates data eagerly, until a cycle is detected. As a result, a deep data structure with a lot of data is generated.

Feature

This issue can be addressed by introducing a new setting Keys.MAX_DEPTH to limit the depth when generating data, e.g.

Settings settings = Settings.create()
    .set(Keys.MAX_DEPTH, 5);

Container container = Instancio.of(Container.class)
    .withSettings(settings)
    .create();
  • Depth 0: blank root object with null fields
  • Depth 1: root object with initialised fields (blank objects and empty collections)
  • etc...

Add support for setting `scale` using `BigDecimal` generator

Currently BigDecimal generator does not support setting scale, resulting in random numbers such as 2.9791836643921816.

Update the generate to allow setting scale as follows:

final int expectedScale = 2;

Invoice invoice = Instancio.of(Invoice.class)
    .generate(all(BigDecimal.class), gen -> gen.math().bigDecimal().min(BigDecimal.ONE).scale(expectedScale))
    .create();

assertThat(invoice.amount.scale()).isEqualTo(expectedScale);

Globally customise generated values based on context

Consider the following:

class Foo {
  private String name;
}

class Bar {
  private String name;
}

I would like to customise how fields called name are generated, without having to specify, or even know about, all of the types that have a field called name.

At the moment, I would have to write this:

var field = field("name");
var generator = gen -> gen.string().mixedCase();

Foo foo = Instancio.of(Foo.class)
    .generate(field, generator)
    .create();
    
Bar bar = Instancio.of(Bar.class)
    .generate(field, generator)
    .create();

But I would like to be able to write:

Instancio customInstancio = Instancio.customise()
  .generate(matching(field -> field.getName().equals("name"), gen -> gen.string().mixedCase());

Foo foo = customIstancio.create(Foo.class);
Bar bar = customInstancio.create(Bar.class);

Equally, I might have this:

class Foo {
  @Id
  private String key;
}

class Bar {
  @Id
  private String id;
}

I would like to customise how fields annotated with Id are generated, without having to specify, or even know about, all of the types that have a field annotated with Id.

I would like to be able to write something like this:

Instancio customInstancio = Instancio.customise()
  .generate(matching(field -> hasAnnotation(field, Id.class), gen -> gen.string().mixedCase());

Foo foo = customIstancio.create(Foo.class);
Bar bar = customInstancio.create(Bar.class);

String generator should support prefixing strings with field name

Current behaviour simply generates random strings. The new feature would allow generating strings prefixed with field name.

For example. String someField would produce a string like "someField_ASFVAF"

Motivation: make inspecting data and debugging easier.

Notes

  • Current behaviour (i.e. no prefix) will remain as the default
  • Field name prefixes can be enabled globally via instancio.properties or per object via Settings

Strings that will not be prefixed

  • strings provided using generate(), set(), and supply() methods

  • null strings when "nullable strings" are enabled using any of the following:

withNullable(field(Person::getName))
withSettings(Settings.create().set(Keys.STRING_NULLABLE, true))
  • Empty strings when "allow empty" is enabled using:
withSettings(Settings.create().set(Keys.STRING_ALLOW_EMPTY, true))

java.lang.OutOfMemoryError when running a test

I first try to use instancio, so please help.
Describe the bug
When I try to create my object, I see an endless this line until the test fail by OutOfMemoryError

DEBUG org.instancio.internal.nodes.NodeFactory - Created superclass type map: {E=}

Stacktrace:

Caused by: java.lang.OutOfMemoryError: Java heap space
	at org.instancio.internal.util.Verify$$Lambda$344/0x0000000800342840.get$Lambda(Unknown Source)
	at java.base/java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(DirectMethodHandle$Holder)
	at java.base/java.lang.invoke.LambdaForm$MH/0x0000000800064840.linkToTargetMethod(LambdaForm$MH)
	at org.instancio.internal.util.Verify.notNull(Verify.java:27)
	at org.instancio.internal.util.TypeUtils.getRawType(TypeUtils.java:61)
	at org.instancio.internal.nodes.NodeFactory.createNodeWithSubtypeMapping(NodeFactory.java:134)
	at org.instancio.internal.nodes.NodeFactory.fromClass(NodeFactory.java:178)
	at org.instancio.internal.nodes.NodeFactory.createNode(NodeFactory.java:76)
	at org.instancio.internal.nodes.NodeFactory.lambda$createChildrenFromFields$2(NodeFactory.java:303)
	at org.instancio.internal.nodes.NodeFactory$$Lambda$391/0x0000000800367440.apply(Unknown Source)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
	at org.instancio.internal.nodes.NodeFactory.createChildrenFromFields(NodeFactory.java:305)
	at org.instancio.internal.nodes.NodeFactory.fromClass(NodeFactory.java:199)
	at org.instancio.internal.nodes.NodeFactory.createNode(NodeFactory.java:76)
	at org.instancio.internal.nodes.NodeFactory.lambda$createContainerNodeChildren$1(NodeFactory.java:295)
	at org.instancio.internal.nodes.NodeFactory$$Lambda$393/0x0000000800366c40.apply(Unknown Source)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
	at org.instancio.internal.nodes.NodeFactory.createContainerNodeChildren(NodeFactory.java:297)
	at org.instancio.internal.nodes.NodeFactory.fromParameterizedType(NodeFactory.java:221)
	at org.instancio.internal.nodes.NodeFactory.createNode(NodeFactory.java:78)

Sample code reproducing the issue

 User user1 = Instancio.create(User.class);
  • Instancio version: 2.8

Static initializer

Does instancio support lcaess with static initlizer?
For example

class Address {
private String name;
private Address (){}
public static Address  of(){
return new Address(); 
}

class Address2 {
private String name;
private Address (String name){this.name=name;}
public static Address  of(String name){
return new Address(name); 
}

class Person {
private Address address;
private Address2 address2;
private Person(){}
public static of() {
return new Person();
}

I have a dependency with such classes, So I cannot change their structure
I tried to generate data with instancio
But as a result, I have a lot of warnings and an error
The library does not support the work, or did I do something wrong? =(

`BigDecimal` generator produces trailing zeroes with large scale

When BigDecimal generator scale is set to a high number, e.g. 20, it produces trailing zeroes:

BigDecimal result = Gen.math().bigDecimal().range(
                new BigDecimal("100000000"),
                new BigDecimal("999999999"))
        .scale(20)
        .get();

// sample: 852752478.13836610000000000000

Expected result
Should produce non-zero fractional digits

Add support for setting a global seed value

Background

Currently seed value can be specified in the following ways:

1. builder API

Example example = Instancio.of(Example.class)
    .withSeed(123)
    .create();

2. @Seed annotation when using InstancioExtension with JUnit 5

@Test
@Seed(123)
void example() {
    Example example = Instancio.create(Example.class);
}
  • The builder API allows creating an individual object with a given seed.
  • The @Seed annotation allows specifying a seed for all create() invocations within a single test method (for reproducing a failed test).

Currently there is no way to specify a seed for all generated instances. This enhancement is to add support for this use case, which would allow running all tests against a fixed data set.

Possible solutions for specifying a global seed

  1. Using a system property as suggested by @Bananeweizen here
  2. Using instancio.properties.

Things to consider

  • Would the system property take precedence over the value set using @Seed annotation or withSeed(...) method?
  • Does it makes sense to implement both?
  • Pros and cons of each approach

`TypeVariable` field declared in generic superclass is not populated in child class

Given the following classes:

class Item<T extends Enum<T>> {
    private T value;

    T getValue() { return value; }
}

enum Foo { FOO }

Item can be populated using a TypeToken, e.g.

Item<Foo> item = Instancio.create(new TypeToken<Item<Foo>>() {});
assertThat(item.getValue()).isNotNull();

However, populating a subclass does not work:

class FooItem extends Item<Foo> {
  // ...
}

FooItem item = Instancio.create(FooItem.class);
assertThat(item.getValue()).isNotNull(); // fails! value is null

Investigate adding support for class instantiated via static factory methods

Currently there's no generic way to generate classes that are instantiated via static factory methods. For example, Optional and EnumSet have special logic to deal with them. Such special cases reduce maintainability and also complicate adding support for other similar classes, e.g. Guava collections.

Cannot generate value of type that is subtype of field class

Describe the bug
String generator cannot generate value for field of type java.lang.Object

Expected result
String value assigned to Object field

Actual result
org.instancio.exception.InstancioApiException:
Generator type mismatch:
Method 'string()' cannot be used for type: java.lang.Object

Additional information
It seems that this trouble is caused by incorrect usage of method Class.isAssignableFrom in GeneratorSupport.supports:

final Class<?> typeArg = TypeUtils.getGenericSuperclassTypeArgument(generator.getClass());
if (typeArg != null) {
    return typeArg.isAssignableFrom(type)
        || PrimitiveWrapperBiLookup.findEquivalent(typeArg)
        .filter(type::isAssignableFrom)
        .isPresent();
}

from method documentation of isAssignableFrom:

Determines if the class or interface represented by this Class object is either the same as, or is a superclass or superinterface of, the class or interface represented by the specified Class parameter.

so it seems that it should be type.isAssignableFrom(typeArg) since the typeArg is assigned to type.

Sample code reproducing the issue

Instancio.of(Request.class)
            .generate(field("objectValue"), Generators::string)
            .create();
  • Instancio version: 2.9.0

Customise generated values based on content

Sometimes, in order for generated data to be valid, constraints need to be specified across multiple fields.

For example:

class Foo {
  String firstName;

  String fullName;
}

I want to ensure that fullName is prefixed with firstName;

I would like to be able to write:

Instancio.of(Foo.class)
  .generate(field("fullName"), gen -> foo -> gen.string().prefix(foo.getFirstName() + " ")
  .create();

I would like fields with specs specified in this way to be applied after other fields have been generated and in the order specified, so that fields that do not have dependencies can be generated first, and then depended on by later fields.

Improvement, use typed accessors instead of field names

This is not a bug, but a wish.

Instead of relying on field names, do you think it would be possible to use method references ?

// Actual
Instancio.of(Person.class)
    .generate(field("dateOfBirth"), gen -> gen.temporal().localDate().past())
    .generate(field(Phone.class, "areaCode"), gen -> gen.oneOf("604", "778"))

// Wished
Instancio.of(Person.class)
    .generate(field(Person::getDateOfBirth), gen -> gen.temporal().localDate().past())
    .generate(field(Phone.class, Phone::getAreaCode"), gen -> gen.oneOf("604", "778"))

TypeVariable is not resolved when using parameterized generic subtype in a class

Describe the bug
When having a generic subtype inside a class X, Instancio is not able to resolve the TypeVariable to the expected mappedType though the type is parameterized in X. Issue is similar to #202.

Expected result
Instancio is able to resolve mappedTypes when using a generic subtype, which is
parameterized over a concrete class.
Instancio is able to set a value for this resolved type.

Actual result
Instancio is not able to resolve a type variable, when using a generic subtype.

[main] WARN org.instancio.internal.nodes.NodeFactory - Unable to resolve type variable 'T'. Parent: Node[GenericDummy.foo, depth=1, #chn=0, GenericFoo<Long>]

Additional information
NodeFactory#resolveTypeVariable returns null when confronted with a generic subtype.
The determined mappedType for T such cases is T.

parent.getTypeMap().getOrDefault(typeVar, typeVar)

mappedType=T and typeVar=T.
T is therefore resolved with T.

Sample code reproducing the issue

public class GenericBar<T> {
    T value;

    public T getValue() {
        return value;
    }
}

public class GenericFoo<T> extends GenericBar<T> {
}

public class Dummy {

    private GenericFoo<Long> foo;

    public GenericFoo<Long> getFoo() {
        return foo;
    }
}

The following test fails:

Dummy dummy = Instancio.create(Dummy.class);
assertThat(dummy.getFoo().getValue()).isNotNull(); // fails - value is null

Changing the type GenericFoo to GenericBar inside Dummy (=> Use generic type instead of generic subtype) works well.
In this case T is resolved to Long as mappedType in NodeFactory#resolveTypeVariable.

  • Instancio version: 2.7.0

Request for Documentation on Contributing to Instancio

Feature description

Dear Instancio Project Contributors,

I am interested in contributing to the Instancio Project and would like to learn more about how to get started. I have reviewed the existing resources but am unable to find clear and concise instructions on how to begin.

It would be greatly appreciated if you could provide me with a step-by-step guide or documentation on how to contribute to the project, including information on the project's contribution guidelines, code of conduct, and the technical requirements for making a contribution.

Thank you for your time and I look forward to the opportunity to contribute to the Instancio Project.

Migrate seed from integer to long

Currently seed value is represented by int. This is inconsistent withjava.util.Random, which uses long.

Update the API to use long (this will be a breaking change).

Subtype mapping provided via `Settings` is not picked up

Example:

class SetHolder {
    private Set<String> values; // + getter
}

It should be possible to specify implementation type using Settings as follows:

Settings settings = Settings.create().mapType(Set.class, TreeSet.class);

However, currently this is broken:

SetHolder result = Instancio.of(SetHolder.class)
    .withSettings(settings)
    .create();

assertThat(result.getValues()).isExactlyInstanceOf(TreeSet.class); // fails - actual is a HashSet

Generating an `EnumMap` returns `null`

Creating an EnumMap returns null:

Instancio.create(new TypeToken<EnumMap<MyEnum, String>>() {});

Suppressed error:

[main] DEBUG o.i.i.u.ExceptionHandler.logSuppressed:75 - Suppressed error because 'instancio.failOnError' system property is disabled
java.lang.NullPointerException: Cannot read the array length because "this.this$0.vals" is null
	at java.base/java.util.EnumMap$EnumMapIterator.hasNext(EnumMap.java:519)
	at org.instancio.internal.InstancioEngine.generateMap(InstancioEngine.java:209)
	at org.instancio.internal.InstancioEngine.createObject(InstancioEngine.java:101)
	at org.instancio.internal.InstancioEngine.lambda$createRootObject$0(InstancioEngine.java:84)
	at org.instancio.internal.util.ExceptionHandler.conditionalFailOnError(ExceptionHandler.java:38)
	at org.instancio.internal.InstancioEngine.createRootObject(InstancioEngine.java:83)
	at org.instancio.internal.InstancioApiImpl.create(InstancioApiImpl.java:156)
	at org.instancio.Instancio.create(Instancio.java:177)
        ...

Infinite loop creating Nodes from many classes with references to each other

Describe the bug
We got a pretty huge code base with many classes having Collections with references to eachother.
When creating an object from these entities with Instancio it seems like creating the nodes takes a lot of time (havent seen it finish yet, so infinit loop). (Relatet to Issue #399 )
Changing the Max Depth through the properties file or directly in the Test doesnt change the behaviour, because the maxDepth is only checked when creating/generating the objects in org/instancio/internal/InstancioEngine.java:125.
But the infinit loop seems to be while creating the Nodes in org/instancio/internal/nodes/NodeFactory.java:75
("Stage One" refering to #391 (comment))

Expected result

  • Nodes are only created till the maxDepth is reached(?)
  • No infinit loop
  • Object is created and populated with data

Actual result
"infinit loop"

Additional information
I tried restricting the maxDepth or adding several classes to "ignore", but the nodes are still created for these classes

Sample code reproducing the issue
Little bit extreme, i know ;)

	@Test
	void instancioShouldCreateObject() {
		// given
		// when
		ObjectA objectA = Instancio.of(ObjectA.class)
				.create();
		// then
		assertThat(objectA).hasNoNullFieldsOrProperties();
	}

	class ObjectA {
		List<ObjectB> objectB;
		List<ObjectC> objectC;
		List<ObjectD> objectD;
		List<ObjectE> objectE;
		List<ObjectF> objectF;
		List<ObjectG> objectG;
	}

	class ObjectB {
		ObjectA objectA;
		ObjectC objectC;
		ObjectD objectD;
		ObjectE objectE;
		ObjectF objectF;
		ObjectG objectG;
	}

	class ObjectC {
		ObjectA objectA;
		ObjectB objectB;
		ObjectD objectD;
		ObjectE objectE;
		ObjectF objectF;
		ObjectG objectG;
	}

	class ObjectD {
		ObjectA objectA;
		ObjectB objectB;
		ObjectC objectC;
		ObjectE objectE;
		ObjectF objectF;
		ObjectG objectG;
	}

	class ObjectE {
		ObjectA objectA;
		ObjectB objectB;
		ObjectC objectC;
		ObjectD objectD;
		ObjectF objectF;
		ObjectG objectG;
	}

	class ObjectF {
		ObjectA objectA;
		ObjectB objectB;
		ObjectC objectC;
		ObjectD objectD;
		ObjectE objectE;
		ObjectG objectG;
	}

	class ObjectG {
		ObjectA objectA;
		ObjectB objectB;
		ObjectC objectC;
		ObjectD objectD;
		ObjectE objectE;
		ObjectF objectF;
	}
  • Instancio version: 2.8.0

Seed not loaded from instancio.properties

Describe the bug
The seed value is not loaded from instancio.properties.

It uses always a random one.

Expected result
It should use the seed specified on instancio.properties.

Actual result
Uses a random one.

  • Instancio version: 2.2.0

Add generator for `ZoneOffset`

Currently ZoneOffset fields are not populated. Implement generators for creating random values.

assertThat(Instancio.create(ZoneOffset.class)).isNotNull();

How can I supply a whole custom instance as a field?

Hello everyone!

First I want to thank you for this awesome library, it's so useful and semantic. I have a question, I was trying to build an instance from my record PC, but I couldn't create it using strict mode. I wrote something like this:

import static org.instancio.Select.field;

import java.util.List;
import org.instancio.Instancio;
import org.instancio.Model;
import org.instancio.Random;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class IssueTest {
  public enum Socket { AMD, INTEL}
  public record CPU(String model, Double frequency){}
  public record RAM(String model, Integer capacity){}
  public record MainBoard(String model, Socket socket, String chipset){}

  record PC(MainBoard board, CPU cpu, List<RAM> memories){}

  static CPU customCPU(Random random){
    String model = random.alphanumeric(10).concat("_cpu");
    Double frequency = random.doubleRange(3.0, 5.0);
    return new CPU(model, frequency);
  }

  static MainBoard customMainBoard(Random random){
    String model = random.alphanumeric(10).concat("_board");
    Socket socket = random.oneOf(Socket.values());
    String chipset = random.alphanumeric(5).concat("_socket");

    return new MainBoard(model, socket, chipset);
  }

  static List<RAM> customMemories(Random random){
    String model = random.alphanumeric(15).concat("_ram");
    Integer capacity = random.oneOf(4, 8, 16, 32);
    RAM memory = new RAM(model, capacity);
    return List.of(memory);
  }

  @Test
  void customisedPC() {
    Model<PC> model =  Instancio.of(PC.class)
        .supply(field("cpu"), IssueTest::customCPU)
        .supply(field("board"), IssueTest::customMainBoard)
        .supply(field("memories"), IssueTest::customMemories)
        .lenient() //It's necessary to run.
        .toModel();

    PC pc = Instancio.create(model);
    System.out.println(pc);
    
    Assertions.assertTrue(pc.memories.get(0).model.contains("_ram"), "It wasn't generated by a custom generator.");
    Assertions.assertFalse(pc.cpu.model.contains("_cpu"), "It was generated by a custom generator.");
  }

}

Is possible to supply a whole custom field? I saw that if I use generators for one-by-one of the fields on the PC then it would work. Thanks in advance.

Add support for capturing the seed value

Background

When using InstancioExtension with JUnit 5, test failure message includes the seed value used to generate the data, e.g.

Test method 'foo' failed with seed: 123

This allows the data to be reproduced re-running the test with the reported seed value using the @Seed(123) annotation. However, there is currently no way to capture the seed value when not using the extension (e.g. using Instancio standalone or with JUnit 4).

Deliverables

Add support for capturing the seed value when creating an object.

Example example = Instancio.of(Example.class)
    // ...
    .create();

// what was the seed value??

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.