Reputation: 6679
I am trying to figure out which would be the most functional style for this situation
I have a Image model
case class Image(
id: Int,
name: String,
title: String,
permalink: String,
url: String
)
I have a TestHelper object that helps me when I write tests, because it allows me to create random images objects
package utils
import models.Pet
import scala.util.Random
object TestHelper {
val random = new Random()
def randomId = random.nextInt(Integer.MAX_VALUE)
val nameList: List[String] = List("Joycelyn", "Shaunte", "Aurelio", "Jeane", "Carline", "Major", "Shawanna", "Hayden", "Benjamin", "Roxy", "Ardelia", "Yanira", "Tilda", "Claude", "Jonah", "Ilse", "Kieth", "Elmira", "Reid", "Bethann", "Catherine", "Yasuko", "Kia", "Merri", "Ethelyn", "Mallory", "Eustolia", "Matt", "Lynelle", "Christi", "Alane", "Miles", "Ressie", "Darryl", "Kathy", "Hiedi", "Kacy", "Cecila", "Tamar", "Dwayne", "Charlette", "Wanetta", "Sonja", "Celine", "Vina", "Teresa", "Dagny", "Debera", "Doreatha", "Wilda")
def randomImage: Image = {
var id = randomId
var name = nameList(random.nextInt(nameList.length))
var title = name
var permalink = name.toLowerCase
var logoUrl = s"https://www.images.com/${permalink}"
Image(id, name, title, permalink, logoUrl)
}
}
But I know that if I want to write in a functional style I should avoid using var
. If I wouldn't use the field name
, several times, it would be enough to replace all the var
s with def
s, but since I need to repeat the value, I am not sure how to write this in a functional style
Upvotes: 1
Views: 678
Reputation: 44957
In this particular case, you can simply replace all local var
s by val
s, because you are not mutating var
s anyway.
Upvotes: 1
Reputation: 28511
Take a look at one of our libs(shameless disclaimer).
util-samplers
https://github.com/outworkers/util/blob/develop/util-samplers
It uses macros to navigate the structure of your case classes and generate appropriate samples. It's not a magic bullet but it will deal with most things most of the time, and it will also generate meaningful data wherever possible.
E.g if the field is called name, you will get a "Peter Smith" style result. It's also fully compatible with Scalacheck, but overall pretty basic, with a very simple macro. It's simplicity is guaranteed by having had me write it.
val imageGenerator = Sample.generator[Image]
implicit val imageArb = Sample.arbitrary[Image]
And you can plug that implicit in straight to your functional checkers.
forAll { img: Image => ....
}
If you don't want scalacheck at all, just use the basics:
import com.outworkers.util.samplers._
class MyTest extends FlatSpec {
it should "upload an image to S3" in {
val image = gen[Image]
val images = genList[Image](25)
}
}
If you cannot generate a type or the macro complains, simply write a sampler yourself. In most instances, you'd have something like a trait or object to hold all of them.
object ExtraSamples {
implicit val cantAutomateThis: Sample[java.net.Bla] = new Sample[java.net.Bla] {
override def sample: java.net.Bla = // in here you fill it in manuall....
}
}
Then if you have a case class
with a java.net.Bla
field, you simply import ExtraSamples._
in places where you do gen
, and your manual implementation will be used to construct more complex ones. That's how you can support anything not supported out of the box.
scalacheck-shapeless
This is a different take on the same problem but instead of macros it uses automated typeclass instance derivation capabilities from shapeless. It's not wildly different in its approach from util-samplers
, but the code might be slightly more complex, yet higher level.
https://github.com/alexarchambault/scalacheck-shapeless
import org.scalacheck.ScalacheckShapeless._
// If you defined:
// case class Foo(i: Int, s: String, blah: Boolean)
// case class Bar(foo: Foo, other: String)
// sealed trait Base
// case class BaseIntString(i: Int, s: String) extends Base
// case class BaseDoubleBoolean(d: Double, b: Boolean) extends Base
// then you can now do
implicitly[Arbitrary[Foo]]
implicitly[Arbitrary[Bar]]
implicitly[Arbitrary[Base]]
I've never done a side to side comparison, and they are not intended to compete with each other. The first one is extremely fast and lightweight and has minimal overhead as it's just one macro, the shapeless one is more involved and comes with much higher compilation times but it's likely more advanced in terms of what types it can auto-generate.
Upvotes: 3
Reputation: 33083
You can use ScalaCheck for this. ScalaCheck is a port of the functional language Haskell's library QuickCheck, which allows you to write random test example generators in a functional style.
Upvotes: 2