Generating and Mocking Data With MockNeat
Let's take a look at MockNeat — a new library designed to help you generate and mock app data tailored to fit your business requirements.
Join the DZone community and get the full member experience.
Join For FreeMockNeat is a new Java 8+ library that can be used to provision apps with initial sets of data that are valid from a business standpoint.
Data is arbitrarily generated by matching a set of criteria that can be programmatically defined, allowing developers enough flexibility to obtain specific arbitrary results matching their business needs.
Let's start with a simple example where we generate a List<String>, where each String contains a first and last name concatenated with a random number. Additional constraints:
- The first name is in lower case letters;
- The last name is in upper case letters;
- The random number has a:
- 30% chance of being in the [0, 10) range;
- 70% chance of being in the [10, 20) range;
- The list contains exactly 1000 strings.
With MockNeat, all we need to do is:
MockNeat m = MockNeat.threadLocal();
// Create the random number generator
MockUnitInt num = m.probabilites(Integer.class)
.add(0.3, m.ints().range(0, 10))
.add(0.7, m.ints().range(10, 20))
.mapToInt(Integer::intValue);
// Creates the arbitrary strings and keep them in a List
List<String> strings = m.fmt("#{first} #{last} #{num}")
.param("first", m.names().first().format(LOWER_CASE))
.param("last", m.names().last().format(UPPER_CASE))
.param("num", num)
.list(1000)
.val();
Possible output:
karol KUMFER 15
rosita SCHNITTKER 15
cori RULON 12
ilda HERMEZ 11
jon ZECCHINI 19
...
Installation
If you use Gradle in your projects, add the following dependency to the Gradle build file:
repositories { jcenter() }
dependencies { compile 'net.andreinc.mockneat:mockneat:0.1.3' }
And here is the equivalent Maven pom.xml dependency:
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.andreinc.mockneat</groupId>
<artifactId>mockneat</artifactId>
<version>0.1.3</version>
</dependency>
</dependencies>
For the latest version, always check the existing jcenter() repository. The current version (0.1.3 at time of writing) should be stable enough, but the library is being actively developed, so it's best to keep an eye on new releases.
Getting Started
The starting point of the library is the MockNeat.java class.
The simplest way to obtain a MockNeat instance is to re-use one of the "pre-defined" ones:
/** Internally uses the ThreadLocalRandom implementation */
MockNeat mock = MockNeat.threadLocal();
/** Internally uses the SecureRandom implementation */
MockNeat mock = MockNeat.secure();
/** Internally uses the classical Random implementation */
MockNeat mock = MockNeat.old();
Once we have the instance, we can start generating our first types of data:
// Generating an arbitrary integer in the range [200, 100) and then divide it by 5
Integer int1 = mock.ints()
.range(100, 200)
.map(i -> i / 5)
.val();
// Possible Output: 35
// Generate an IPV4 address of Class A or CLASS C
String ipv4ClassA = mock.ipv4s()
.types(CLASS_A, CLASS_C)
.val();
// Possible Output: 213.25.234.95
To see a complete list of all the types of data that can be generated, please check the official documentation. The library covers, by default, a comprehensive list for generating primitives values, financial data, networking data, user-related data, geographical data, etc.
Everything Is a MockUnit<T>
The way the data is obtained is by chaining a set of constraints (like we did with the range(), type(), etc. in the previous examples). Each method in the chain passes the behavior to the next one while re-using the behavior of the previous one.
In all examples, you will see the val() method called last. This is the closing method of the chain, the one that generates the actual value.
All generators implement the functional interface MockUnit<T>. The full documentation on this interface can be found here.
Once we've configured a generator, we can keep referencing it and re-use it to obtain multiple values:
// Creates a MockUnitInt that generates
// integers in the interval [0, 10)
MockUnitInt integers = mock.ints().bound(10);
// Each subsequent call to val() returns a different value
int x1 = integers.val(),
x2 = integers.val(),
x3 = integers.val();
System.out.printf("%d %d %d\n", x1, x2, x3);
// Possible Output:
// 7 3 4
We can re-use the generators further. Collections and arrays can also be created by translating the previous behavior of a MockUnit<T> into a new MockUnit<List<T>> and so on:
// Generator of integer values
MockUnitInt integers = mock.ints().bound(10);
// Generates a primitive int array of length 10
// where all the integers are between [0, 10)
int[] arr1 = integers.arrayPrimitive(10).val();
// Generates an Integer[] array of length 10
// where all the integers are between [0, 10)
Integer[] arr2 = integers.array(10).val();
// Generates a LinkedList of size 10
// where all the integers are between [0, 10)
List<Integer> list1 = integers.list(LinkedList.class, 10).val();
// Generate a List<List<Integer>>
// where all the integers from the enclosed lists
// are between [0, 10)
List<List<Integer>> list2 = integers.list(10).list(10).val();
As you can see in the last example, complex data structures can be instantly created.
Let's assume we want to mock a shocking structure like a Map<String, List<Map<Set<Integer>, List<Integer>>>>. Normally you shouldn't have something like that in a real-world application, so this is for demonstration purposes only:
Map<String, List<Map<Set<Integer>, List<Integer>>>> result =
mock.ints() // A MockUnitInt that randomly generates Integers
.list(2) // Translates to MockUnit<List<Int>>
.mapKeys(2, mock.ints().set(3)::val) // Translates to MockUnit<Map<Set<Integer>, List<Integer>>>
.list(LinkedList.class, 2) // Translates to MockUnit<List<Map<Set<Integer>, List<Integer>>>>
.mapKeys(4, mock.strings()::val) // Translates to MockUnit<Map<String, List<Map<Set<Integer>, List<Integer>>>>>
.val(); // Gets a single value from the generator (MockUnit<..>)
By morphing into a different MockUnit<T1 - T2 -...-Tn> each time we, can build crazy structures filled with arbitrary data defined by our initial sets of constraints.
Normally, this should cover the basic needs for mocking complex objects from your model layers.
Mocking Real-Life Objects
Let's assume we have two classes — User and Group — that are associated through the groupId field (each user belongs to a group, and the groupId on the User class acts like a foreign key).
// No Arg Constructor
// Getters and Setters
public class User {
private Long id;
private Long groupId;
private String email;
}
// No Arg Constructor
// Getters and Setters
public class Group {
private Long id;
private String name;
}
We can use the reflect() method to mock the objects. This method internally uses the Reflection API.
First, we create a list of groups with random names in the following format: "Group [A-Z]{3}[0-0]{2}" (using the regex() method, we can generate arbitrary something that matches a given regular expression).
The groupId values will be a sequence of numbers [100, 200, 300, 400, ...].
// Create a list of 5 groups
List<Group> groups = mock.reflect(Group.class)
.field("id", mock.longSeq().start(100).increment(100))
.field("name", mock.regex("Group [A-Z]{3}[0-9]{2}"))
.list(5)
.val();
// Output
// [Group{id=100, name='Group VTD01'}, Group{id=200, name='Group OOK98'}, Group{id=300, name='Group LKE18'}, Group{id=400, name='Group KMU53'}, Group{id=500, name='Group HCE89'}]
The second step is to create the list of 100 Users referencing the groupId from the previous example:
// Generating 100 users
List<User> users = mock.reflect(User.class)
.field("id", mock.longSeq())
.field("groupId", mock.from(groups).map(Group::getId))
.field("email", mock.emails())
.list(100)
.val();
// Possible Output:
// [User{id=0, groupId=500, email='darnedjeffery@hotmail.co.uk'}, User{id=1, groupId=300, email='bluntisabell@mail.com'}, User{id=2, groupId=300, email='beigeerin@hotmail.co.uk'}, ... ]
Going Further: Generating CSV Files With Arbitrary Data
In the following example, we are going to generate a .csv file contain "financial information" for a set of random individuals.
The format of the CSV file is as follows:
id, firstName, lastName, email, salary, creditCardNum
We will use the following methods:
- longSeq() to generate the id;
- names().first() to generate the firstName;
- names().last() to generate the lastName;
- emails() to generate the email;
- ints().range() to generate the salary;
- creditCards() to generate the creditCardNum;
The solution:
MockNeat m = MockNeat.threadLocal();
final Path path = Paths.get("./test.csv");
m.fmt("#{id},#{first},#{last},#{email},#{salary},#{creditCardNum}")
.param("id", m.longSeq().start(10).increment(10))
.param("first", m.names().first())
.param("last", m.names().last())
.param("email", m.emails().domain("company.com"))
.param("salary", m.ints().range(1000, 5000))
.param("creditCardNum", m.creditCards().type(AMERICAN_EXPRESS))
.list(1000)
.consume(list -> {
try { Files.write(path, list, CREATE, WRITE); }
catch (IOException e) { e.printStackTrace(); }
});
And the results are as follows:
10,Zandra,Sprenger,triedshakira@company.com,2578,371618033623968
20,Davina,Shamapande,lothadelia@company.com,4953,373956318938315
30,Cinthia,Brotherton,hetnicklaw@company.com,1921,377270429060092
40,Ashton,Rochkes,skintbirches@company.com,3318,373400437933865
50,Mammie,Scagliotti,chargedsigrid@company.com,4719,375698819073725
60,Leone,Rings,deafclifford@company.com,4311,344775717545851
70,Mika,Tengan,limbedtimmy@company.com,3993,378324590541684
80,Shaun,Starns,featdart@company.com,4750,375645508553061
90,Ninfa,Dompe,markedpozzies@company.com,3630,346404509489586
100,Lea,Genzone,softlyprigs@company.com,4667,372555622778341
110,Michal,Boesen,fierceeileen@company.com,3692,343596540625129
120,Prudence,Perro,wholesaleflues@company.com,2761,374100480248117
130,Tena,Carl,wingedsuzann@company.com,4437,374096736417177
140,Temeka,Wools,ainkim@company.com,2087,372318008657138
150,Marybeth,Piepenbrink,frorelenard@company.com,3623,348735745292633
160,Dorie,Lippard,cheerlessolivares@company.com,1786,378499736153337
170,Joann,Boardman,plunktransects@company.com,1432,347952346559697
...
Going Further: Generating JSON Strings
By default, MockNeat doesn't support JSON transformations at the MockUnit interface level. The solution is to work with one of the existing libraries. For simplicity, in the following example, we are going to use gson.
The model classes are as follows:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Profile {
Integer profileId;
Date profileAdded;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserProfile {
String name;
String userName;
String email;
List<Profile> profiles;
}
The easiest way to generate a JSON String from the UserProfile class:
MockNeat mockNeat = MockNeat.threadLocal();
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
String json = mockNeat
.reflect(UserProfile.class)
.field("name", mockNeat.names().full())
.field("userName", mockNeat.users())
.field("email", mockNeat.emails())
.field("profiles",
mockNeat.reflect(Profile.class)
.field("profileId", mockNeat.ints().range(100, 1000))
.field("profileAdded", mockNeat.localDates().toUtilDate())
.list(2))
.map(gson::toJson) /* Transforms the UserProfile class into a 'pretty' json. */
.val();
System.out.println(json);
And the possible output:
{
"name": "Cecila Starbird",
"userName": "moistben",
"email": "randiexyst@hotmail.co.uk",
"profiles": [
{
"profileId": 964,
"profileAdded": "Mar 19, 1973 12:00:00 AM"
},
{
"profileId": 854,
"profileAdded": "Jun 3, 1978 12:00:00 AM"
}
]
}
Opinions expressed by DZone contributors are their own.
Comments