Combining ScalaCheck generators

ScalaCheck library comes with a wide variety of built-in generators, but let me show you how to combine them to make the library even more powerful.

Map on generator

Generator comes with a method map which allows you to transform obtained data. Let's create a generator which generates only positive, even integers:

val evenNumberGenerator: Gen[Int] = Gen.posNum[Int].map(_ * 2)
`

In this case map accepts a function Int => U, since the generator on which the map is called returns Int. You can chain maps even further:

val oddNumberGenerator: Gen[Int] = evenNumberGenerator.map(_ + 1)

FlatMap on generator

Let's suppose that you wold like to generate Either[String, Int]. We can use flatMap to handle this case. We will create Right if generated number is even and left if it's odd:

  val leftOrRightFiftyFifty: Gen[Either[String, Int]] = Gen.posNum[Int].flatMap{ i =>
    if(i % 2 == 0) Gen.posNum[Int].map(Right[String, Int])
    else Gen.alphaLowerStr.map(Left[String, Int])
  }

FlatMap called on generator accepts a function T => Gen[U]. This allows you to combine generators further and further, leading to the generator which exactly suits your needs.

Frequency generator

FlatMap is a very powerful tool, but requires a lot of boilerplate. What would happen if we wanted to obtain right 40% of time? What if we would have more and more possibilities? There is a nice built-in solution for this case. Take a look at frequency combinator:

val leftOrRight: Gen[Either[String, Int]] = Gen.frequency(
    4 -> Gen.posNum[Int].map(Right[String, Int]),
    6 -> Gen.alphaLowerStr.map(Left[String, Int])
)

Options here and options there

Option generators are well represented in ScalaCheck. If you want to always generate Some[T] you can use Gen.some like this:

val someOfIntGenerator: Gen[Option[Int]] = Gen.some(Gen.posNum[Int])

In case you would like to have Some and None you can use Gen.option, to have both of the possibilities. It's used in exactly the same way as Gen.some:

val optionalIntegerGenerator: Gen[Option[Int]] = Gen.option(Gen.posNum[Int])

What is interesting Gen.option is implemented using Gen.frequency. Check it out here.

Exclusive ranges

There might be a possibility that you want to generate a number from non overlapping ranges. Again you can use a nice built-in generator - Gen.oneOf. In this example we will generate numbers greater than 10 and smaller than 20 or bigger than 40 and smaller than 50. It's as simple as this:

val oneOfRanges: Gen[Int] = Gen.oneOf(
    Gen.chooseNum(10, 20),
    Gen.chooseNum(40, 50)
)

Conclusion

Hopefully i managed to show you that ScalaCheck generators are easily combined, leading to code which can fit your exact needs.

Keep in mind that the examples I shown above are rather simple, but I hope you can already see usages which will fit your domain requirements.