Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a random instance of a case class?

Suppose I've got a few case classes, e.g.:

case class C(c1: Int, c2: Double, c3: Option[String])
case class B(b: Int, cs: Seq[C])
case class A(a: String, bs: Seq[B]) 

Now I would like to generate a few instances of A with random values for tests.

I am looking for a generic way to do that. I can probably do it with runtime reflection but I prefer a compile-time solution.

def randomInstance[A](a: A): A = ???

How can I do it ? Can it be done with shapeless ?

like image 811
Michael Avatar asked Jun 03 '18 12:06

Michael


People also ask

Is it easy to create a random class?

This is not an easy task. Different classes are created in different ways. They might have constructors that require other objects. Let’s see how one might create a random class —an instance created using the random constructor, to which we pass random data. We will do it step-by-step in a TDD way (Test Driven Development).

What is a case class in Java?

A case class has all of the functionality of a regular class, and more. When the compiler sees the case keyword in front of a class, it generates code for you, with the following benefits:

How to generate pseudo random numbers in Java?

Java.util.Random class in Java. Random class is used to generate pseudo random numbers in java. Instance of this class are thread safe. The instance of this class are however cryptographically insecure. This class provides various method call to generate different random data types such as float, double, int.

Can you create an instance of a class for testing purposes?

Kt. Academy During our last Advent of Kotlin, there was one particularly tricky moment where we had to create an instance of any class for testing purposes. This is not an easy task. Different classes are created in different ways. They might have constructors that require other objects.


Video Answer


2 Answers

We've started using Magnolia, which provides a faster type class derivation compared to shapeless for derivation of Arbitrary instances.

Here is the library to use, and here is an example (docs):

case class Inner(int: Int, str: String)
case class Outer(inner: Inner)

// ScalaCheck Arbitrary
import magnolify.scalacheck.auto._
import org.scalacheck._ // implicit instances for Arbitrary[Int], etc.

val arb: Arbitrary[Outer] = implicitly[Arbitrary[Outer]]
arb.arbitrary.sample
// = Some(Outer(Inter(12345, abcde)))
like image 63
Anish Avatar answered Oct 23 '22 03:10

Anish


We've just moved away from scalacheck-shapeless and use Scala/Java reflection instead.

The main reasons are (1) scalacheck-shapeless uses Macros (slow compilation), (2) the API is a bit more verbose than my liking, and (3) the generated values are way too wild (e.g. generating strings with Japanese characters).

However, setting it up is a bit more involved. Here is a full working code that you can copy into your codebase:

import scala.reflect.api
import scala.reflect.api.{TypeCreator, Universe}
import scala.reflect.runtime.universe._

object Maker {
  val mirror = runtimeMirror(getClass.getClassLoader)

  var makerRunNumber = 1

  def apply[T: TypeTag]: T = {
    val method = typeOf[T].companion.decl(TermName("apply")).asMethod
    val params = method.paramLists.head
    val args = params.map { param =>
      makerRunNumber += 1
      param.info match {
        case t if t <:< typeOf[Enumeration#Value] => chooseEnumValue(convert(t).asInstanceOf[TypeTag[_ <: Enumeration]])
        case t if t =:= typeOf[Int] => makerRunNumber
        case t if t =:= typeOf[Long] => makerRunNumber
        case t if t =:= typeOf[Date] => new Date(Time.now.inMillis)
        case t if t <:< typeOf[Option[_]] => None
        case t if t =:= typeOf[String] && param.name.decodedName.toString.toLowerCase.contains("email") => s"[email protected]"
        case t if t =:= typeOf[String] => s"arbitrary-$makerRunNumber"
        case t if t =:= typeOf[Boolean] => false
        case t if t <:< typeOf[Seq[_]] => List.empty
        case t if t <:< typeOf[Map[_, _]] => Map.empty
        // Add more special cases here.
        case t if isCaseClass(t) => apply(convert(t))
        case t => throw new Exception(s"Maker doesn't support generating $t")
      }
    }

    val obj = mirror.reflectModule(typeOf[T].typeSymbol.companion.asModule).instance
    mirror.reflect(obj).reflectMethod(method)(args:_*).asInstanceOf[T]
  }

  def chooseEnumValue[E <: Enumeration: TypeTag]: E#Value = {
    val parentType = typeOf[E].asInstanceOf[TypeRef].pre
    val valuesMethod = parentType.baseType(typeOf[Enumeration].typeSymbol).decl(TermName("values")).asMethod
    val obj = mirror.reflectModule(parentType.termSymbol.asModule).instance

    mirror.reflect(obj).reflectMethod(valuesMethod)().asInstanceOf[E#ValueSet].head
  }

  def convert(tpe: Type): TypeTag[_] = {
    TypeTag.apply(
      runtimeMirror(getClass.getClassLoader),
      new TypeCreator {
        override def apply[U <: Universe with Singleton](m: api.Mirror[U]) = {
          tpe.asInstanceOf[U # Type]
        }
      }
    )
  }

  def isCaseClass(t: Type) = {
    t.companion.decls.exists(_.name.decodedName.toString == "apply") &&
      t.decls.exists(_.name.decodedName.toString == "copy")
  }
}

And, when you want to use it, you can call:

val user = Maker[User]
val user2 = Maker[User].copy(email = "[email protected]")

The code above generates arbitrary and unique values. They aren't exactly randomised. It's best for using in tests.

Read our full blog post here: https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html

like image 39
Tanin Avatar answered Oct 23 '22 04:10

Tanin