Reputation: 11334
I'm working with a piece of code that has a broad/deep case class hierarchy. For unit testing, I'd like to have "random data" populated in the classes with the ability to change the data for the fields I care about?
Example:
case class Foo(bar: Bar, name: String, value: Int)
case class Bar(baz: Baz, price: Double)
case class Baz(thing: String)
So something like:
val randomFoo = GenerateRandomData(Foo)
randomFoo.bar.baz = Baz("custom for testing")
I've heard of ScalaCheck
and Shapeless
and Scalacheck-shapeless
and they do provide some sort of random data generation but nothing with customization it seems.
I'm currently using ScalaMock
but that builds out null
fields and breaks testability for "other" tests. I used something similar in .Net like Auto Fixture and was wondering if there was something similar in Scala.
Upvotes: 4
Views: 2204
Reputation: 4283
Your question has two parts. It is possible to automatically generate classes in ScalaCheck with Gen.resultOf
. This was previously asked in scalacheck case class random data generator. It may be possible to have scalacheck-shapeless do it with even less boilerplate.
The second part of your question is about mutating fields in immutable objects. For the most part, just using copy
constructor provided by case class
is sufficient. A library like Monocle can help, but I've never had to use it.
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import org.scalacheck.Prop.AnyOperators // Adds ?= operator
import org.scalacheck.Properties
import org.scalacheck.Gen
object FooSpec extends Properties("Foo") {
val genBaz: Gen[Baz] = Gen.resultOf(Baz)
implicit val arbBaz = Arbitrary(genBaz)
val genBar: Gen[Bar] = Gen.resultOf(Bar)
implicit val arbBar = Arbitrary(genBar)
val genFoo: Gen[Foo] = Gen.resultOf(Foo)
implicit val arbFoo = Arbitrary(genFoo)
val genCustomFoo: Gen[Foo] = {
Arbitrary.arbitrary[Foo].map { foo =>
foo.copy(bar = foo.bar.copy(baz = Baz("custom for testing")))
}
}
property("arbFoo") = {
Prop.forAll { foo: Foo =>
foo.bar.baz.thing != "custom for testing"
}
}
property("genCustomFoo") = {
Prop.forAll(genCustomFoo) { foo: Foo =>
foo.bar.baz.thing ?= "custom for testing"
}
}
}
Upvotes: 0
Reputation: 2851
Scalacheck
does offer a const generator, that allow to define customized / constant strings:
import org.scalacheck._
val fooGen: Gen[Foo] =
for {
baz <- Gen.const("custom for testing").map(Baz)
price <- Gen.choose[Double](0, 5000)
name <- Gen.alphaStr
value <- Gen.choose(0, 100)
} yield {
val bar = Bar(baz, price)
Foo(bar, name, value)
}
Here is what we get when we run it:
scala> fooGen.sample
res6: Option[Foo] = Some(
Foo(
Bar(Baz("custom for testing"), 1854.3159675078969),
"EegNcrrQyzuazqrkturrvsqylaauxausrkwtefowpbkptiuoHtdfJjoUImgddhsnjuzpoiVpjAtjzulkMonIrzmfxonBmtZS",
64
)
)
Update : As @Dima pointed out, a way to derive random values for all fields is to use [scalacheck-shapeless]
(https://github.com/alexarchambault/scalacheck-shapeless) and lenses
for the customization, here is an example that uses Monocle
:
import org.scalacheck.{Arbitrary, Gen}
import monocle.Lens
import org.scalacheck.ScalacheckShapeless._
implicitly[Arbitrary[Foo]]
val lensBar = Lens[Foo, Bar](_.bar)(bar => _.copy(bar = bar))
val lensBaz = Lens[Bar, Baz](_.baz)(baz => _.copy(baz = baz))
val lensThing = Lens[Baz, String](_.thing)(thing => _.copy(thing = thing))
val lens = (lensBar composeLens lensBaz composeLens lensThing).set("custom for testing")
val fooGen: Gen[Foo] = Arbitrary.arbitrary[Foo].map(lens)
println(fooGen.sample)
// Display
// Some(Foo(Bar(Baz(custom for testing),1.2227226413326224E-91),〗❌䟤䉲㙯癏<,-2147483648))
Upvotes: 4
Reputation: 40510
I think, you are looking for scalaz lense.
It'll do what you want.
However, I gotta say, that using random data for unit testing seems like a horrible idea. How are you going to debug a failure that happens every now and again?
You should invest some time into setting up a deterministic set of constant test objects, that also resemble the actual production data, and then just use that in your tests.
Upvotes: 5