agusgambina
agusgambina

Reputation: 6679

Scala create random Objects for test in a functional style

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 vars with defs, but since I need to repeat the value, I am not sure how to write this in a functional style

Upvotes: 1

Views: 678

Answers (3)

Andrey Tyukin
Andrey Tyukin

Reputation: 44957

In this particular case, you can simply replace all local vars by vals, because you are not mutating vars anyway.

Upvotes: 1

flavian
flavian

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

Robin Green
Robin Green

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

Related Questions