Instancio is a Java library for generating test objects. Its main goal is to reduce manual data setup in unit tests. Its API was designed to be as non-intrusive and as concise as possible, while providing enough flexibility to customise generated objects. Instancio requires no changes to production code, and it can be used out-of-the-box with zero configuration.
There are several existing libraries for generating realistic test data, such as addresses, first and last names, and so on. While Instancio also supports this use case, this is not its goal. The idea behind the project is that most unit tests do not care about the actual values. They just require the presence of a value. Therefore, the main goal of Instancio is simply to generate fully populated objects with random data, including arrays, collections, nested collections, generic types, and so on. And it aims to do so with as little code as possible in order to keep the tests concise.
Another goal of Instancio is to make the tests more dynamic. Since each test run is against random values, the tests become alive. They cover a wider range of inputs, which might help uncover bugs that may have gone unnoticed with static data.
Finally, Instancio aims to provide reproducible data. It uses a consistent seed value for each object graph it generates. Therefore, if a test fails against a given set of inputs, Instancio supports re-generating the same data set in order to reproduce the failed test.
This section provides an overview of the API for creating and customising objects.
The Instancio class is the entry point to the API. It provides the following shorthand methods for creating objects. These can be used when defaults suffice and generated values do not need to be customised.
The following builder methods allow chaining additional method calls in order to customise generated values, ignore certain fields, provide custom settings, and so on.
The three arguments accepted by these methods can be used for different purposes.
It should be noted that generic types can also be created using the
Instancio.of(Class) method and specifying the type parameters manually:
However, this approach has a couple of drawbacks: it does not supported nested generics, and its usage will generate an "unchecked assignment" warning.
1.5.0 introduced support for creating
recordclasses when run on Java 16+, and
sealedclasses when run on Java 17+.
This uses the same API as described above for creating regular classes.
Creating a Stream of Objects¶
Instancio also provides methods for creating a
Stream of objects.
stream() methods return an infinite stream of distinct fully-populated instances.
Similarly to the
create() methods, these have a shorthand form if no customisations are needed:
as well as the builder API that allows customising generated values:
|Stream Builder API|
The following are a couple of examples of using streams. Note the calls to
which are required to avoid an infinite loop.
|Examples of stream() methods|
Since returned streams are infinite,
limit() must be called to avoid an infinite loop.
Selectors are used to target fields and classes, for example in order to customise generated values. Selectors are provided by the Select class which contains the following methods:
|Static methods for targeting fields and classes|
allXxx() methods such as
allInts(), are available for all core types.
The above methods return either an instance of Selector or SelectorGroup type. The latter is a container combining multiple Selectors. For example, to ignore certain values, we can specify them individually as follows:
|Examples of using selectors|
or alternatively, we can combine the selectors into a single group:
|Examples of using a selector group|
Field selectors have higher precedence than class selectors. Consider the following example:
|Selector precedence example|
This will produce a person object with all strings set to "foo". However, since field selectors have higher precedence, person's name will be set to "bar".
Selectors also offer the
within(Scope... scopes) method for fine-tuning which targets they should be applied to.
The method accepts one or more
Scope objects that can be creating using the static methods in the
|Static methods for specifying selector scopes|
To illustrate how scopes work we will assume the following structure for the
Person class structure¶
|Sample POJOs with getters and setters omitted|
To start off, without using scopes we can set all strings to the same value. For example, the following snippet will set each string field of each class to "foo".
|Set all strings to "Foo"|
within() we can narrow down the scope of the
allStrings() selector. For brevity,
Instancio.of(Person.class) line will be omitted.
within() also allows specifying multiple scopes. Scopes must be specified top-down, starting from the outermost to the innermost.
Person.work address object contains a list of phones, therefore
Person.work is the outermost scope and is specified first.
Phone class is the innermost scope and is specified last.
Instancio supports two modes: strict and lenient, an idea inspired by Mockito's highly useful strict stubbing feature.
In strict mode unused selectors will trigger an error. In lenient mode unused selectors are simply ignored.
By default, Instancio runs in strict mode. This is done for the following reasons:
- to eliminate errors in data setup
- to simplify fixing tests after refactoring
- to keep test code clean and maintainable
Eliminate errors in data setup¶
An unused selector could indicate an error in the data setup. As an example, consider populating the following POJO:
If we want to create the POJO with a set of size 10, it might be tempting to do the following:
This, however, will not work. The field is declared as a
SortedSet, but the selector is for
For the selector to match, the target class must be the same as the field's:
Without being aware of this detail, it is easy to make this kind of error and face unexpected results even with a simple class like above. It gets trickier when generating more complex classes. Strict mode helps reduce this type of error.
Consider another example where the problem may not be immediately obvious:
List<Phone> is contained within the
Address class, the
generate() method is redundant
Person class structure outlined at the beginning of this section).
All addresses will be null, therefore no phone instances will be created.
Strict mode will trigger an error highlighting this problem.
Simplify fixing tests after refactoring¶
Somewhat related to the above is refactoring. Refactoring always causes test failures to some degree. Classes get reorganised and tests need to be updated to reflect those changes. Assuming there are existing tests utilising Instancio, running tests in strict mode will quickly highlight any problems in data setup caused by refactoring.
Keep test code clean and maintainable¶
Last but not least, it is important to keep tests clean and maintainable. Test code should be treated with as much care as production code. Keeping the tests concise makes them easier to maintain.
While strict mode is highly recommended, there is an option to switch to lenient mode.
The lenient mode can be enabled using the
Person person = Instancio.of(Person.class) .lenient() // snip... .create(); Lenient mode can also be enabled via `Settings`. In fact, the `lenient()` method above is a shorthand for the following: ``` java title="Setting lenient mode using Settings" Settings settings = Settings.create() .set(Keys.MODE, Mode.LENIENT); Person person = Instancio.of(Person.class) .withSettings(settings) // snip... .create();
Lenient mode can also be enabled globally using
Properties of an object created by Instancio can be customised using
methods defined in the InstancioApi class.
generate() method provides access to built-in generators for core types from the JDK, such strings, numeric types, dates, arrays, collections, and so on.
It allows modifying generation parameters for these types in order to fine-tune the data.
The usage is shown in the following example, where the
gen parameter (of type Generators) exposes the available generators to simplify their discovery using IDE auto-completion.
|Example of using generate()|
Each generator provides methods applicable to the type it generates, for example:
Below is another example of customising a
For instance, if the
Person class has a field
List<Phone>, by default Instancio would use
ArrayList as the implementation.
Using the collection generator, this can be overridden by specifying the type explicitly:
set() method is self-explanatory.
It can be used to set a static value to selected fields or classes, for example:
|Example of using set()|
countryCode to "+1" on all generated instances of
LocalDateTime values to
supply() method has two variants:
Using supply() to provide non-random values¶
The first variant can be used where random values are not appropriate and the generated object needs to have a meaningful state.
countryCode to "+1" for all instances of
LocalDateTime instances will be distinct objects with the value
There is some overlap between the
For instance, the following two lines will produce identical results:
set() is just a convenience method to avoid using
supply() when the value is constant.
supply() method can be used to provide a new instance each time it is called.
For example, the following methods are not identical:
Person class has multiple
LocalDateTime fields, using
set() will set them all to the same instance, while using
supply() will set them all to distinct instances.
This difference is even more important if supplying a
Collection, since sharing a collection instance among multiple objects is usually not desired.
Using supply() to provide random values¶
The second variant of the
supply() method can be used to generate random objects.
This method takes a Generator as an argument, which is a functional interface with the following signature:
Using the provided Random instance ensures that Instancio will be able to reproduce the generated object when needed.
The Random implementation uses a
java.util.Random internally, but offers a more user-friendly interface and convenience methods not available in the JDK class.
|Creating a custom Generator|
PhoneGenerator can now be passed into the
Instancio also offers a Service Provider Interface, GeneratorProvider that can be used to register custom generators.
This removes the need for manually passing custom generators to the
supply method as in the above example.
They will be picked up automatically.
supply() method provides an instance of Random, the method can also be used for customising values of core type, such as strings and numbers.
generate() method should be preferred in such cases if possible as it provides a better abstraction and would result in more readable code.
|generate() vs supply()|
random to generate a
Generated objects can also be customised using the OnCompleteCallback, a functional interface with the following signature:
The following example shows how the
Address can be modified using a callback.
Person has a
List<Address>, the callback will be invoked for every instance of the
Address class that was generated.
|Example: modifying an object via a callback|
The advantage of callbacks is that they can be used to update multiple fields at once. The disadvantage, however, is that they can only be used to update mutable types.
It should also be noted that callbacks are only invoked on non-null values.
In the following example, all address instances are nullable.
Therefore, a generated address instance may either be
null or a fully-populated object.
However, if a
null was generated, the callback will not invoked.
|Callbacks only called on non-null values|
Ignoring Fields or Classes¶
By default, Instancio will attempt to populate every non-static field value.
ignore method can be used where this is not desirable:
|Example: ignoring certain fields and classes|
ignore() method has higher precedence than other methods. For example, in the following snippet
ignore(all(LocalDateTime.class)) but supplying a value for the
will actually generate a
lastModified with a
|ignore() has higher precedence than other methods|
By default, Instancio generates non-null values for all fields.
There are cases where this behaviour may need to be relaxed, for example to verify that a piece of code does not fail in the presence of certain
There are a few way to specify that values can be nullable.
This can be done using:
withNullablemethod of the builder API
- generator methods (if a generator supports it)
To specify that something is nullable using the builder API can be done as follows:
|Example: specifying nullability using the builder API|
Some built-in generators also support marking values as nullable. In addition, Collection, Map, and Array generators allow specifying whether elements, keys or values are nullable.
|Example: specifying nullability using the collection generator|
Person class contains a
Map, nullability can be specified for keys and values:
|Example: specifying nullability using the map generator|
Lastly, nullability can be specified using Settings, but only for core types, such as strings and numbers:
|Example: specifying nullability using Settings|
Subtype mapping allows mapping a particular type to its subtype.
This can be useful for specifying a specific implementation for an abstract type.
The mapping can be specified using the
All the types represented by the selectors must be supertypes of the given
|Example: subtype mapping|
Pet is an abstract type, then without the mapping all
Pet instances will be
since Instancio would not be able to resolve the implementation class.
Person has an
Address field, where
Address is the superclass of
A Model is a template for creating objects which encapsulates all the generation parameters specified using the builder API. For example, the following model of the Simpson's household can be used to create individual Simpson characters.
Model class does not expose any public methods, and its instances are effectively immutable.
However, a model can be used as template for creating other models.
The next example creates a new model that includes a new
|Example: using a model as a template for creating other models|
Before creating an object, Instancio initialises a random seed value.
This seed value is used internally by the pseudorandom number generator, that is,
Instancio ensures that the same instance of the random number generator is used throughout object creation, from start to finish.
This means that Instancio can reproduce the same object again by using the same seed.
This feature allows reproducing failed tests (see the section on reproducing tests with JUnit).
In addition, Instancio takes care in generating values for classes like
LocalDateTime, where a minor difference in values can cause an object equality check to fail.
These classes are generated in such a way, that for a given seed value, the generated values will be the same.
To illustrate with an example, we will use the following
By supplying the same seed value, the same object is generated:
|Generating two SamplePojo instances with the same seed|
If the objects are printed, both produce the same output:
While the generated values are the same, it is not recommended to write assertions using hard-coded values.
Specifying Seed Value¶
By default, Instancio uses a random seed to generate an object. This behaviour can be overridden using any of the following options:
@WithSettingsannotations (when using
InstancioExtensionfor JUnit Jupiter)
- withSeed(int seed) method of the builder API
These are ranked from lowest precedence to highest. Seed value passed to
withSeed() takes precedence over other values, such as those supplied through properties or
Seed value specified through properties is a "global" seed. All objects created by Instancio will use this seed (unless the seed is overridden using one of the other methods). This will result in the same data being generated on each run.
Getting Seed Value¶
Sometimes it is necessary to get the seed value that was used to generate the data. One such example is for reproducing failed tests. If you are using JUnit 5, seed value is reported automatically using the
InstancioExtension (see JUnit Jupiter integration). If you are using JUnit 4, TestNG, or Instancio standalone, the seed value can be obtained by calling the
asResult() method of the builder API. This returns a
Result containing the created object and the seed value that was used to populate its values.
|Example of using asResult()|
This section expands on the Selectors section, which described how to target fields. Instancio uses reflection at field level to populate objects. The main reason for using fields and not setters is type erasure. It is not possible to determine the generic type of method parameters at runtime. However, generic type information is available at field level. In other words:
Without knowing the list's generic type, Instancio would not be able to populate the list. For this reason, it operates at field level. Using fields, however, has one drawback: they require the use of field names. To circumvent this problem, Instancio includes an annotation processor that can generate metamodel classes.
The following example shows two selectors for the
city field of
Address, one referencing the field by name, and the other using the generated metamodel class:
_ is used as the metamodel class suffix, but this can be customised using
Configuring the Annotation Processor¶
To configure the annotation processor with Maven, add the
<annotationProcessorPaths> element to the build plugins section in your
pom.xml as shown below.
You still need to have the Instancio library, either
instancio-junit, in your
<dependencies> (see Getting Started).
The following can be used with Gradle version 4.6 or higher, add the following to your
With the annotation processor build configuration in place, metamodels can be generated using the @InstancioMetamodel annotation. The annotation can be placed on any type, including an interface as shown below.
It is not recommended declaring the
@InstancioMetamodel annotation with the same
classes more than once.
Doing so will result in metamodels being generated more than once as well.
For this reason, it is better to have a dedicated class containing the
Metamodels for classes specified in the annotation will be automatically generated during the build.
Typically metamodels are placed under a generated sources directory, such as
If your IDE does not pick up the generated classes, then adding the generated sources directory to the build path
(or simply reloading the project) should resolve this.
Instancio configuration is encapsulated by the Settings class, a map of keys and corresponding values.
Settings class provides a few static methods for creating settings.
|Settings static factory methods|
other settings (a clone operation).
Settings can be overridden programmatically or through a properties file.
To inspect all the keys and default values, simply:
Overriding Settings Programmatically¶
To override programmatically, an instance of
Settings can be passed in to the builder API:
|Supplying custom settings|
lock() method makes the settings instance immutable. This is an optional method call.
It can be used to prevent modifications if settings are shared across multiple methods or classes.
Range settings auto-adjust
When updating range settings, such as
range bound is auto-adjusted if the new minimum is higher than the current maximum, and vice versa.
The Keys class defines a property key for every key object, for example:
Using these property keys, configuration values can also be overridden using a properties file.
Overriding Settings Using a Properties File¶
Default settings can be overridden using
instancio.properties. Instancio will automatically load this file from the root of the classpath. The following listing shows all the property keys that can be configured.
map.values.nullable specify whether Instancio can generate
null values for array/collection elements and map keys and values.
*.nullable properties specifies whether Instancio can generate
null values for a given type.
LENIENT. See Selector Strictness.
subtype are used to specify default implementations for abstract types, or map types to subtypes in general.
This is the same mechanism as subtype mapping, but configured via properties.
Instancio layers settings on top of each other, each layer overriding the previous ones. This is done in the following order:
- Settings from
- Settings injected using
@WithSettingsannotation when using
InstancioExtension(see Settings Injection)
- Settings supplied using the builder API's withSettings(Settings) method
In the absence of any other configuration, Instancio uses defaults as returned by
instancio.properties is found at the root of the classpath, it will override the defaults. Finally, settings can also be overridden at runtime using
@WithSettings annotation or withSettings(Settings) method. The latter takes precedence over everything else.
JUnit Jupiter Integration¶
Instancio supports JUnit 5 via the InstancioExtension and can be used in combination with extensions from other testing frameworks. The extension adds a few useful features, such as
- the ability to use @InstancioSource with
- injection of custom settings using @WithSettings,
- and most importantly support for reproducing failed tests using the @Seed annotation.
Reproducing Failed Tests¶
Since using Instancio validates your code against random inputs on each test run, having the ability to reproduce a failed tests with previously generated data becomes a necessity.
Instancio supports this use case by reporting the seed value of a failed test in the failure message using JUnit's
Seed Lifecycle in a JUnit Jupiter Test¶
Instancio initialises a seed value before each test method. This seed value is used for creating all objects during the test method's execution, unless another seed is specified explicitly using the withSeed(int seed) method.
|Seed Lifecycle in a JUnit Test|
8276 goes out of scope.
person3 are created using the same seed value of
8276, they are actually distinct objects, each containing different values. This is because the same instance of the random number generator is used througout the test method.
Test Failure Reporting¶
When a test method fails, Instancio adds a message containing the seed value to the failed test's output. Using the following failing test as an example:
|Test failure example|
The failed test output will include the following message:
The failed test can be reproduced by using the seed reported in the failure message. This can be done by placing the @Seed annotation on the test method:
|Reproducing a failed test|
@Seed annotation in place, the data becomes effectively static.
This allows the root cause to be established and fixed.
Once the test is passing, the
@Seed annotation can be removed so that new data will be generated on each subsequent test run.
InstancioExtension also adds support for injecting Settings into a test class.
The injected settings will be used by every test method within the class.
This can be done using the @WithSettings annotation.
There can be only one field annotated
@WithSettings per test class.
Instancio also supports overriding the injected settings using the
withSettings method as shown below.
The settings provided via the method take precedence over the injected settings (see Settings Precedence for further information).
@WithSettings placed on static and non-static fields.
However, if the test class contains a
@ParameterizedTest method, then the settings field must be static.
Using the @InstancioSource annotation it is possible to have arguments provided directly to a
@ParameterzedTest test method.
This works with a single argument and multiple arguments, each class representing one argument.
@ParameterizedTest requires the
|Using @InstancioSource with @ParameterizedTest|
It should be noted that using
@InstancioSource has a couple of important limitations that makes it unsuitable in many situations.
The biggest limitation is that the generated objects cannot be customised. The only option is to customise generated values using settings injection. However, it is not possible to customise values on a per-field basis, as you would with the builder API.
The second limitation is that it does not support parameterized types.
For instance, it is not possible to specify that
@InstancioSource(List.class) should be of type