Some time ago I wrote a piece about generating randomized instances of domain objects for unit and integration testing using a functional approach. Looking back at my past projects, I almost always used this approach to simplify existing and newly written tests in the codebases that I was working on. Of course, the implementation matured over the years as well. What started off as a very simplistic @FunctionalInterface became a well-tested Java-based implementation of the Gen monad.

Given that I use this small piece of code so often, I decided to release the Gen monad to the public.

Why?

Typically, the Gen monad is a concept used in property-based testing libraries, such as ScalaCheck and the like. Property-based tests verify statements about the output of your code base on some given input, where the same statement is verified for many admissible and inadmissible inputs alike. Such tests rely heavily upon randomly generated objects. But even if you do not commit to property-based testing as a concept in your testsuite, having abstractions for randomly generating objects from your domain can simplify testing a lot.

In a typical unit test large portions of your tests consist of methods that construct test fixtures. Even if you put in a good amount of work in designing your test fixtures, it is cumbersome to provide the means for their parameterization, as composability of test fixtures is an issue. The Gen monad is composable by design, making it easy to randomly generate complex objects from a couple of small methods.

Oftentimes you find yourself in need for a test fixture for some class, but the actual test logic only cares about a single parameter that goes into the constructor of that class. You still have to come up with values for the rest of the class members, even if they provide no value for the test. Not only does this increase the code inside your test case, but it clouds the distinction between things that are relevant for that particular test and those that are not. A generator-based approach can help here as well.

What changed?

The introduction to functional testing that I wrote in 2016 focuses on how to use the features of Java 8 to design and implement functional abstractions. Hence, the described implementation Gen is simplistic. Instead of designing Gen as a @FunctionalInterface it is implemented as a Java class that closes around the actual generator function and a source of randomness. The source of randomness is currently implemented using java.util.Random. Each of the static factory methods that Gen exports provides two versions of the same method: One that accepts an instance of java.util.Random as source of randomness and one that does not. Using the former lets you inject a specific seed value to your generators. Suppose you had a failing test on a generated domain object using a certain seed. Re-using the same seed allows you to replay that same test instance and analysis the error or implement a dedicated regression test for the bugfix. The combinator methods map and flatMap retain the initial source of randomness.

How to get it

I decided to release the implementation of the Gen monad. If you're interested, you can have a peek at the source code over at GitHub and get the JARs via the following Maven coordinates (with version 1.1.0 being the latest version at the time of publication).

<dependency>
  <groupId>net.mguenther.gen</groupId>
  <artifactId>gen</artifactId>
  <version>${gen.version}</version>
</dependency>

The source code available at GitHub comes with a couple of examples that showcase how the Gen monad can be used. The examples are also explained in the README.md.

Disclaimer

One can argue that the Gen monad is not really a monad as it does not satisfy equality in the monad laws equationally. It does however hold "morally", if we consider a generator Gen<T> to be a probability distribution over values of type T.

Hi there! I'm Markus!

I'm an independent freelance IT consultant, a well-known expert for Apache Kafka and Apache Solr, software architect (iSAQB certified) and trainer.

How can I support you?

GET IN TOUCH