0

Our case classes have several require statements to throw exceptions when they receive malformed input. This is normally pretty helpful, but can make writing property-based tests a pain, since we have to write generators that satisfy all the requirements rather than simply using the built-in generators. Is there an easy way to set Scala to ignore require statements during testing?

Satvik Beri
  • 271
  • 1
  • 2
  • 9
  • *[...] we have to write generators that satisfy all the requirements rather than simply using the built-in generators*. Why would you not ensure that the generators you use produce instances that satisfy the class's invariants? Besides, you should consider eschewing `require` altogether (as you point out, it throws exceptions, so it isn't very functional) and centralising validation and instantiation of `Foo` in a creation method that returns a `Try[Foo]`. – jub0bs Dec 23 '16 at 11:38

2 Answers2

3

A much better approach would be to avoid require entirely, and instead write smart constructors that return Either (or Validation if you're scalaz-inclined.) Better still would be to create newtype wrappers (i.e. case class MyWrapper private (i: Int) extends AnyVal) for your input types, where those wrappers come with smart constructor such that only valid values can be constructed. When you write generators for these bespoke types, you can ensure that the invariants you need are preserved.

Rather than look for a way around the type system (by using require to throw exceptions at runtime) let the type system work for you. This will make not only writing your property-based tests much easier, but the entirety of your system will benefit.

Kris Nuttycombe
  • 4,560
  • 1
  • 26
  • 29
  • Interesting, `Validation` seems like the way to go for my use case–the require statements aren't *super* important, and simply logging the errors is probably good enough. I'm mostly trying to avoid writing *any* generators though, because we have enough datatypes that it would be a really substantial undertaking. – Satvik Beri Nov 02 '16 at 04:53
  • 1
    I'm curious... Could you expand on "Better still would be to create newtype wrappers (i.e. `case class MyWrapper private (i: Int) extends AnyVal)` for your input types, where those wrappers come with smart constructor such that only valid values can be constructed." +1, BTW – jub0bs Dec 23 '16 at 11:46
2

No - require is a very simple method, as you can see from the source:

def require(requirement: Boolean) {
  if (!requirement)
    throw new IllegalArgumentException("requirement failed")
}

@inline final def require(requirement: Boolean, message: => Any) {
  if (!requirement)
    throw new IllegalArgumentException("requirement failed: "+ message)
}

I'd suggest providing a separate way to construct your class that only the tests use. For example, if you scope the constructor to the package:

package foo

final case class Foo private[foo] (...)

object Foo {
    def apply(...): Foo = /* put assertions in here */
}

Then you could put your scalacheck generators in the same package and let them use the private constructor.

Or if you really want to get weird with it, you could write your own require that determines whether to skip the checks based on some global state or an implicit parameter. But this seems unwise.

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
  • *I'd suggest providing a separate way to construct your class that only the tests use.* I vigorously disagree. If your tests are bypassing the application code, what's the point? Controlling instantiation using a factory/creation method in application code, and using that method in generators is the way to go, IMO. – jub0bs Dec 23 '16 at 11:42