ScalaCheck custom generator examples

Scala testing tip: Writing custom generators for ScalaCheck can be one of the more difficult and/or time-consuming parts of using it. As a result I thought I’d start putting together a list of generators that I have written or seen elsewhere. Unfortunately I can’t credit all the ones I’ve seen in other places because I google’d and copied them many moons ago, but I’ll give credit/attribution to all the ones I can.

Custom generators

This is a combination of generators I wrote, and some that I copied from other places and may have modified a little:

//TODO hack; need a better way to go from String -> Gen[String]
def stringWithManyBlanks: Gen[String] = Gen.oneOf(
    genRandomVariableLengthStringWithBlankSpaces(r),
    genRandomVariableLengthStringWithBlankSpaces(r)
)

val threeLetters: Gen[Seq[Char]] = Gen.pick(3, 'A' to 'Z')
val genStringStream = Gen.containerOf[Stream,String](Gen.alphaStr)
val evenInteger = Arbitrary.arbitrary[Int] suchThat (_ % 2 == 0)
def badStateGen: Gen[String] = Gen.choose(73, 99).toString
val genNonEmptyString: Gen[String] = Gen.alphaStr.suchThat(i => !i.isEmpty)
val genNonEmptyNumString: Gen[String] = Gen.numStr.suchThat(i => !i.isEmpty)

// generates a string of ASCII characters, with extra weighting for printable characters.
// bad: generates characters that make the terminal beep.
val asciiStr = Gen.asciiStr
// generates a string of ASCII printable characters.
// this is better, but it doesn’t generate many blanks.
val asciiPrintableStr = Gen.asciiPrintableStr

Here are some integer generators I wrote:

import org.scalacheck.Gen.const
import org.scalacheck.Prop.forAll
import org.scalacheck.{Gen, Properties}

object GenIntSeq {

    // (1) generate lists that contain the numbers 1 through 5
    val g1to5: Gen[List[Int]] = Gen.containerOf[List,Int](Gen.choose(1, 5))

    // (2) generate lists using a frequency, where 2s occur four times as often
    // as the numbers 1, 3, 4 and 5
    val favorTwos: Gen[Int] = Gen.frequency(
        (1, 1),
        (4, 2),
        (1, 3),
        (1, 4),
        (1, 5)
    )
    val genMostlyTwos: Gen[List[Int]] = Gen.containerOf[List,Int](favorTwos)

    // (3) another way to try to control how many 2s are in each list.
    // it improves on what's shown on this page:
    // stackoverflow.com/questions/38922414/how-can-i-define-a-scalacheck-generator-that-produces-a-subset-of-a-sequences-e
    val littleList: List[Int] = scala.util.Random.shuffle(List(1,2,3,4,5,6,2,7,8,9))
    val littleListGen: Gen[List[Int]] = Gen.someOf(littleList).map(_.toList)

    // generate a List[String]
    val genStringList = Gen.containerOf[List,String](Gen.alphaStr)

    val littleInts = Gen.choose(0, 99)
    val intsGreaterThan1 = Gen.choose(2, 10000)  //2147483647
    val nonZeroOneInts = Arbitrary.arbitrary[Int] suchThat (i => i != 0 && i != 1)

}

Here are some I wrote for pizza toppings, for my book, Functional Programming, Simplified:

sealed trait Topping
case object BlackOlives extends Topping
case object Cheese extends Topping
case object Mushrooms extends Topping
case object Pepperoni extends Topping
case object Sausage extends Topping

object GenToppingsSeq {

    // generate a list of toppings
    val genBO = const(BlackOlives)
    val genCh = const(Cheese)
    val genMu = const(Mushrooms)
    val genPe = const(Pepperoni)
    val genSa = const(Sausage)

    def genTopping: Gen[Topping] = Gen.oneOf(genBO, genCh, genMu, genPe, genSa)
    val genToppings: Gen[List[Topping]] = Gen.containerOf[List,Topping](genTopping)

}

More ScalaCheck generators

Here are some more ScalaCheck generators I wrote will developing property-based tests for my Scala 3 StringUtils project. Note that some of these are duplicates of the generators shown above (because I’m pasting this in here quickly today):

object GenString {

    val r = new scala.util.Random
    import StringUtils.genRandomVariableLengthStringWithBlankSpaces

    //TODO hack; need a better way to go from String -> Gen[String]
    def stringWithManyBlanks: Gen[String] = Gen.oneOf(
        genRandomVariableLengthStringWithBlankSpaces(r),
        genRandomVariableLengthStringWithBlankSpaces(r)
    )

    def genAlphaStringsLength0To10: Gen[String] = Gen.oneOf(
        for len <- 0 to 10 yield StringUtils.randomAlphanumericString(len)
    )

    /**
      * Generator ideas and examples below here
      * ---------------------------------------
      */
    // val threeLetters: Gen[Seq[Char]] = Gen.pick(3, 'A' to 'Z')
    val genAlphaStream = Gen.containerOf[Stream,String](Gen.alphaStr)
    val evenInteger = Arbitrary.arbitrary[Int] suchThat (_ % 2 == 0)
    def badStateGen: Gen[String] = Gen.choose(73, 99).toString
    val genNonEmptyString: Gen[String] = Gen.alphaStr.suchThat(i => !i.isEmpty)
    val genNonEmptyNumString: Gen[String] = Gen.numStr.suchThat(i => !i.isEmpty)

    // generates a string of ASCII characters, with extra weighting for printable characters.
    // bad: generates characters that make the terminal beep.
    val asciiStr = Gen.asciiStr
    // generates a string of ASCII printable characters.
    // this is better, but it doesn’t generate many blanks.
    val asciiPrintableStr = Gen.asciiPrintableStr
    val simpleString: Gen[String] = Gen.asciiPrintableStr.suchThat(i => !i.isEmpty)

    val genStringList = Gen.containerOf[List,String](Gen.alphaStr)
}

While I’m in this neighborhood, this is how I use one of the ScalaCheck generators in that project, e.g., the generator that generates a list of strings:

property("Q: test multiline strings") = forAll(GenString.genStringList) { (xs: List[String]) =>
    val listOfNonEmptyStrings = xs.filter(_.trim != "")
    val multiLineString = listOfNonEmptyStrings.mkString("\n")
    val rez = Q"$multiLineString"
    rez.size == listOfNonEmptyStrings.size
}

See that project for more ScalaCheck examples.

Built-in ScalaCheck generators

Don’t forget that the org.scalacheck.Gen.scala class has many built-in generators. Here are function signatures and Scaladoc for many of them:

/*-------------------------------------------------------------------------*\
 **  ScalaCheck                                                             **
 **  Copyright (c) 2007-2018 Rickard Nilsson. All rights reserved.          **
 **  http://www.scalacheck.org                                              **
 **                                                                         **
 **  This software is released under the terms of the Revised BSD License.  **
 **  There is NO WARRANTY. See the file LICENSE for the full text.          **
 \*------------------------------------------------------------------------ */

/** Generates a list of random length. The maximum length depends on the
*  size parameter. This method is equal to calling
*  `containerOf[List,T](g)`. */
def listOf[T](g: => Gen[T]) = buildableOf[List[T],T](g)

/** Generates a non-empty list of random length. The maximum length depends
*  on the size parameter. This method is equal to calling
*  `nonEmptyContainerOf[List,T](g)`. */
def nonEmptyListOf[T](g: => Gen[T]) = nonEmptyBuildableOf[List[T],T](g)

/** Generates a list of the given length. This method is equal to calling
*  `containerOfN[List,T](n,g)`. */
def listOfN[T](n: Int, g: Gen[T]) = buildableOfN[List[T],T](n,g)

/** Generates a map of random length. The maximum length depends on the
*  size parameter. This method is equal to calling
*  <code>containerOf[Map,T,U](g)</code>. */
def mapOf[T,U](g: => Gen[(T,U)]) = buildableOf[Map[T,U],(T,U)](g)

/** Generates a non-empty map of random length. The maximum length depends
*  on the size parameter. This method is equal to calling
*  <code>nonEmptyContainerOf[Map,T,U](g)</code>. */
def nonEmptyMap[T,U](g: => Gen[(T,U)]) = ...

/** Generates a map with at most the given number of elements. This method
*  is equal to calling <code>containerOfN[Map,T,U](n,g)</code>. */
def mapOfN[T,U](n: Int, g: Gen[(T,U)]) = ...

/** Generates an infinite stream. */
def infiniteStream[T](g: => Gen[T]): Gen[Stream[T]] = ...

/** A generator that picks a random number of elements from a list */
def someOf[T](l: Iterable[T]) = ...

/** A generator that picks a random number of elements from a list */
def someOf[T](g1: Gen[T], g2: Gen[T], gs: Gen[T]*) = ...

/** A generator that picks at least one element from a list */
def atLeastOne[T](l: Iterable[T]) = ...

/** A generator that picks at least one element from a list */
def atLeastOne[T](g1: Gen[T], g2: Gen[T], gs: Gen[T]*) = ...

/** A generator that picks a given number of elements from a list, randomly */
def pick[T](n: Int, l: Iterable[T]): Gen[Seq[T]] = ...

/** A generator that picks a given number of elements from a list, randomly */
def pick[T](n: Int, g1: Gen[T], g2: Gen[T], gn: Gen[T]*): Gen[Seq[T]] = ...

/** Takes a function and returns a generator that generates arbitrary
*  results of that function by feeding it with arbitrarily generated input
*  parameters. */
def resultOf[T,R0](f: T => R0)(implicit a: Arbitrary[T]): Gen[R0] = ...

/** Creates a Function0 generator. */
def function0[A](g: Gen[A]): Gen[() => A] = ...


//// Character Generators ////

/** Generates a numerical character */
def numChar: Gen[Char] = choose(48.toChar, 57.toChar)

/** Generates an upper-case alpha character */
def alphaUpperChar: Gen[Char] = choose(65.toChar, 90.toChar)

/** Generates a lower-case alpha character */
def alphaLowerChar: Gen[Char] = choose(97.toChar, 122.toChar)

/** Generates an alpha character */
def alphaChar = frequency((1,alphaUpperChar), (9,alphaLowerChar))

/** Generates an alphanumerical character */
def alphaNumChar = frequency((1,numChar), (9,alphaChar))

/** Generates a ASCII character, with extra weighting for printable characters */
def asciiChar: Gen[Char] = chooseNum(0, 127, 32 to 126:_*).map(_.toChar)

/** Generates a ASCII printable character */
def asciiPrintableChar: Gen[Char] = choose(32.toChar, 126.toChar)


//// String Generators ////

/** Generates a string that starts with a lower-case alpha character,
*  and only contains alphanumerical characters */
def identifier: Gen[String] = (for {
c <- alphaLowerChar
cs <- listOf(alphaNumChar)
} yield (c::cs).mkString)

/** Generates a string of digits */
def numStr: Gen[String] = listOf(numChar).map(_.mkString)

/** Generates a string of upper-case alpha characters */
def alphaUpperStr: Gen[String] = listOf(alphaUpperChar).map(_.mkString)

/** Generates a string of lower-case alpha characters */
def alphaLowerStr: Gen[String] = listOf(alphaLowerChar).map(_.mkString)

/** Generates a string of alpha characters */
def alphaStr: Gen[String] = listOf(alphaChar).map(_.mkString)

/** Generates a string of alphanumerical characters */
def alphaNumStr: Gen[String] = listOf(alphaNumChar).map(_.mkString)

/** Generates a string of ASCII characters, with extra weighting for printable characters */
def asciiStr: Gen[String] = listOf(asciiChar).map(_.mkString)

/** Generates a string of ASCII printable characters */
def asciiPrintableStr: Gen[String] = listOf(asciiPrintableChar).map(_.mkString)


//// Number Generators ////

/** Generates positive numbers of uniform distribution, with an
*  upper bound of the generation size parameter. */
def posNum[T](implicit num: Numeric[T], c: Choose[T]): Gen[T] = ...

/** Generates negative numbers of uniform distribution, with an
*  lower bound of the negated generation size parameter. */
def negNum[T](implicit num: Numeric[T], c: Choose[T]): Gen[T] = ...

/** Generates numbers within the given inclusive range, with
*  extra weight on zero, +/- unity, both extremities, and any special
*  numbers provided. The special numbers must lie within the given range,
*  otherwise they won't be included. */
def chooseNum[T](minT: T, maxT: T, specials: T*)(
implicit num: Numeric[T], c: Choose[T]
): Gen[T] = ...


//// Misc Generators ////

/** Generates a version 4 (random) UUID. */
lazy val uuid: Gen[UUID] = ...

lazy val calendar: Gen[Calendar] = ...

val finiteDuration: Gen[FiniteDuration] = ...

/**
* Generates instance of Duration.
*
* In addition to `FiniteDuration` values, this can generate `Duration.Inf`,
* `Duration.MinusInf`, and `Duration.Undefined`.
*/
val duration: Gen[Duration] = ...

Please see the ScalaCheck Gen.scala source code with your IDE or online to see more examples (or more recent examples).

I’ve found that it helps to know what generators are available so you don’t waste your time reinventing the wheel.

How to use ScalaCheck generators

It felt like these examples would be incomplete if I didn’t show how to use custom ScalaCheck generators in property tests, so here are a few examples:

// does not use a custom generator
property("leftTrim") = forAll { s: String =>
    val result = StringUtils.leftTrim(s)
    !result.startsWith(" ")
}

property("rightTrimNeverEndsWithBlankSpace") = forAll(GenString.stringWithManyBlanks) { s: String =>
    val result = StringUtils.rightTrim(s)
    !result.endsWith(" ")
}

property("dropAllButFirstIntLists") = forAll(GenSeq.g1to5) { input: List[Int] =>
    // your property tests here ...
}

property("dropAllButFirstIntLists") = forAll(GenIntSeq.g1to5) ...
property("dropAllButFirstIntLists") = forAll(GenIntSeq.genMostlyTwos) ...
property("dropAllButFirstIntLists") = forAll(GenIntSeq.littleListGen) ...

The Artima website has these examples, which are a little different:

import org.scalacheck.Gen.negNum
import org.scalacheck.Prop.forAll

val propAbs = forAll(negNum[Int]) { n =>
  math.abs(n) == -n
}

import org.scalacheck.Prop.{forAll,BooleanOperators}
import org.scalacheck.Gen.choose

def isPrime(n: Int): Boolean =
  n > 0 && (2 to n).forall(n % _ != 0)

val p1 = forAll(choose(1,100)) { n =>
  isPrime(n) ==> !isPrime(2*n)
}

val p2 = forAll(choose(1,100) suchThat isPrime) { n =>
  !isPrime(2*n)
}

More ScalaCheck generators

You can find even more ScalaCheck generators at these links:

I also wrote about ScalaCheck in my book, Functional Programming, Simplified.