A Java annotation processor that generates randomly populated objects for test use.
EasyModeling is a Java annotation processor that generates randomly populated objects for test use. It could save you from writing tedious code to prepare objects for tests, making your unit tests clearer and more readable. The idea of EasyModeling comes from Martin Fowler's blog ObjectMother. In addition to the concept of ObjectMother, EasyModeling also provides builders of your models so that you can easily customize generated objects for different test scenarios.
EasyModeling provides a set of APIs to help you generate objects for your test, including:
- An annotation
@Model
to tell EasyModeling for which classes you would like to generate objects. - An annotation
@Field
and many annotation attributes, which is however totally optional, to customize the generated objects. - A
next()
method for each class returns randomly populated objects. - A
builder()
method for each class returns randomly populated builders of the objects.
For example, if you have a class Employee
involved in your test so that you want to generate objects for it, you can
firstly have a modeler configuration like this:
@Model(type = Employee.class)
public class Modelers {
}
Then, EasyModeling will create a modeler named EmployeeModeler
for you so that you can use the static next()
method to generate objects:
Employee employee = EmployeeModeler.next();
For a specific test scenario, let's say for a test caring employee's age and marital status, by using static builder()
method, you have the chance to customize the generated objects:
Employee employee = EmployeeModeler.builder().age(30).maritalStatus(SINGLE).build();
EasyModeling starts with the factory pattern and the builder pattern. The main purpose of EasyModeling is to make unit
tests clearer and more readable. Let's assume we have a unit test for annualLeave()
method of the LeaveCalculator
whose responsibility is to calculate the total days of annual leave of an employee from their length of service
at the company. The test could be written as follows:
@Test
void should_get_28_days_for_employees_less_than_5_years_of_service() {
// given (mock up employee details)
Employee employee = new Employee(
23212, // id
"John", // first name
"Smith", // last name
LocalDate.of(1991, 1, 23), // birthdate
MaritalStatus.SINGLE, // marital status
Position.DEVELOPER, // position
List.of( // dependents
new Dependent(...),
new Dependent(...)
new Dependent(...)
),
LocalDate.of(2017, 8, 9), // date of joining --> (1)
...
);
// when
int annualLeave = leaveCalculator.annualLeave(employee); // --> (2)
// then
assertEquals(annualLeave, 28); // --> (3)
}
Several pain points could be encountered when writing unit tests like above.
- The only codes that describe the test scenario are the lines of
(1)
,(2)
and(3)
. Others are just helping mock up the context, which could not help us understand the test. In most cases, it's usual for these unnecessary mock-up codes to become extremely long. - Moreover, most of the mock-up would make the test harder to read. Even in a reasonably scaled system, developers will find they have to create hundreds of verbose mock-up codes like above, which makes our tests neither readable nor maintainable.
- Even if the constructor in the mock-up part could be replaced by some techniques like Builder Pattern, it won't help
reduce the complexity. And it also does not make too much sense to either allow some parameters of the constructor as
nullable or create a builder for the
Employee
class, only for the convenience of the tests. - Regarding the maintainability, imagine we somehow changed any part of the parameter list of the constructor
of
Employee
class, we would have to change all the tests as well.
With the power of EasyModeling, these problems could be solved significantly:
@Test
void should_get_28_days_for_employees_less_than_5_years_of_service() {
// given (mock up employee details)
Employee employee = EmployeeModeler.builder()
.dateOfJoining(LocalDate.of(2017, 8, 9))
.build();
// when
int annualLeave = leaveCalculator.annualLeave(employee);
// then
assertEquals(annualLeave, 28);
}
Java 1.8 or later is required.
EasyModeling is available in Maven Central, so it's easy to ask build tools like Maven or Gradle to integrate it into your projects.
For Gradle users, put the following into your build.gradle file:
dependencies {
...
testImplementation 'io.github.easymodeling:easy-modeling:0.1.3'
testAnnotationProcessor 'io.github.easymodeling:easy-modeling-processor:0.1.3'
...
}
For Maven-based projects, add the following to your POM file:
<dependencies>
<dependency>
<groupId>io.github.easymodeling</groupId>
<artifactId>easy-modeling</artifactId>
<version>0.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>io.github.easymodeling</groupId>
<artifactId>easy-modeling-processor</artifactId>
<version>0.1.3</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
...
Please be aware that EasyModeling is designed for test use, so think twice before using it in production. Using EasyModeling in production will introduce a lot of side effects, such as:
- Security risks: Since EasyModeling generates randoms by using java.util.Random, which is not secure enough compared to more advanced random generators.
- Performance problem: EasyModeling is not optimized for performance. It generates objects by using reflection to achieve the convenience and simplicity of writing tests, regardless of the runtime performance.
- NPE risk: EasyModeling won't promise to populate all fields of the generated objects, although it always tries its best to do so. It's possible that some fields are not populated, which may cause NPE.
Some next steps are in consideration to improve EasyModeling:
- Support some annotations from Bean Validation 2.0 as the customizations of value ranges for generated objects.
- Groovy support.
- Support more widely used data types as basic types of objects population.
Please feel free to create an issues if you have any more exciting ideas.
EasyModeling is Open Source software licensed under the APACHE LICENSE, VERSION 2.0.