0

I'm having trouble getting specs2 and scalacheck to cooperate. I have a simple class, Credit, that takes a single integer within the range of 1 to 59 (anything outside of this range should throw an exception).

I want to define two tests: 1 test that validates the positive cases (any Credit(1) ... Credit(59) validates successfully); and, any Credit outside of that range should fail to validate (it should actually throw an exception when constructed). It seems like the right approach is to define an Arbitrary[Credit] generator, like so:

implicit lazy val arbitraryCreditGenerator: Arbitrary[Credit] = Arbitrary(creditGenerator)

And then I would define an arbitraryBadCreditGenerator too.

The problem is the implicit use of the arbitrary, and trying to figure out the syntax for using prop with my two different arbitrary instances.

What I have so far is:

case class Credit(amount: Int)

object Credit {
    implicit val creditValidator = validator[Credit] { c =>
        c.amount should be > 0
        c.amount should be < 60
    }
} 

class TestCreditModel extends Specification with ScalaCheck { def is = s2"""
   Given a credit
   When validation is performed
   Then credits between 1 and 60 should be acceptable ${creditsAreValid}
   And credits less than 1 or greater than 60 should not ${creditsAreInvalid}
   """

   implicit lazy val arbitraryCreditGenerator: Arbitrary[Credit] = Arbitrary(creditGenerator)

   val creditGenerator = Gen.choose(1,59).map(Credit(_))
   val badCreditGenerator = (Gen.choose(-500, 500) suchThat (n => n < 1 || n > 59)).map(Credit(_))

   def creditsAreValid = prop { (a: Credit) => validate(a).isSuccess must beTrue }

   // Err... oops... how do I use my badCreditGenerator???
   def creditsAreInvalid = prop { (a: badCreditGenerator) => validate(a).isSuccess must beFalse }
   //                              ^ compile error... not defined

}

I'm using Accord for validation, so any attempt to create a Credit with invalid range should fail.

This does not compile, because my use badCreditGenerator is all wrong. I've been fiddling with it and trying to come up with the solution; ideally I'd have two arbitrary generators, one that generates "good" Credit instances and one that tries to generate bad ones.

Maybe there's a better way?

Any help appreciated. Thank you!

Zac
  • 1,278
  • 1
  • 15
  • 27
  • I'll write a longer answer later but you can write `prop { (a: Credit) => ...}.setGen(myGenerator)` to pass in a specific generator. If there are several parameters for your property you want to set you can write `setGens` with all of them or just `setGen1`, `setGen2`... to set one of them. – Eric Aug 23 '16 at 16:45
  • Is there a particular value in using an implicit along with `setGen()` or does it make more sense to simply toss the implicit and go with a `forAll`, which seems to be giving me what I need (and is simpler; no need to define an implicit arbitrary). So right now I'm using `def creditsAreInvalid = forAll(badCreditGenerator) { (c: Credit) => validate(c).isSuccess must beFalse }`. – Zac Aug 23 '16 at 17:22
  • `forAll` works as well. I personally like the `setGen` variations because less rewriting is involved when adding/removing parameters. Also I generally only define one implicit `Arbitrary` for "ok/default" values and use generators for other cases. – Eric Aug 24 '16 at 09:03

0 Answers0