Scala tests with Either and Pattern Matching best practice - scala

I want to test my method which returns an Either. This is how i do this:
#Test def `Test empty name is not valid`: Unit = {
val book = createBook()
val result = insertEntryToBook(book, "", "12345")
result match {
case Left(InvalidNameFormat) => true
case _ => fail()
}
}
Should i do some assert() call instead of fail() to make the test's fail message more explicit (e.g. for assertion side-to-side view)?

There is no reason to pattern match here. You can just assert the result equals the expected value wrapped in a Left().
assertEquals(Left(InvalidNameFormat), result)
In case your test fails, you get a precise error message.

ScalaTest can be used with JUnit, so consider mixing in EitherValues which should give more informative messages
import org.junit.Test
import org.scalatest.{EitherValues, Matchers}
class ExampleSuite extends Matchers with EitherValues {
#Test def matchEithers(): Unit = {
val result: Either[String, Int] = Right(42)
result.left.value should be ("Boom")
}
}
which gives
org.scalatest.exceptions.TestFailedException: The Either on which left.value was invoked was not defined as a Left.
at org.scalatest.EitherValues$LeftValuable.value(EitherValues.scala:124)
at example.ExampleSuite.matchEithers(ExampleSuite.scala:9)
...

Related

How to test exception with Junit 4 and Scala?

I have the following function which I want to test:
def filterFoo(FooColumnName: String, Foo: Seq[Any]): DataFrame = {
/* validate input parameters */
require(Option(FooColumnName).isDefined)
require(Option(Foo).isDefined)
require(df.columns.contains(FooColumnName))
df.filter(col(FooColumnName).isin(Foo:_*))
I have written the following test:
#Test(expected = classOf[IllegalArgumentException])
def testFilterFoorWrongColumnName(): Unit ={
val df = data.toDF(columns:_*)
df.filterFoo(FooColumnName = "foo", Foo = competitors)
}
If FooColumnName does not exists in Data frame it would throw IllegalArgumentException. I am getting this exception but it is failing the test because of this. I get this error when I run the test:
java.lang.IllegalArgumentException: requirement failed
The trick is to reify the exception into a value, which, thankfully, is something that is extremely idiomatic in Scala. scala.util.Try is in the standard library and exceptionally useful for this:
import scala.util.{ Failure, Success, Try }
#Test(expected = classOf[IllegalArgumentException])
def testFilterFoorWrongColumnName(): Unit ={
val df = data.toDF(columns:_*)
val attempt = Try { // note the capital T, this is not the keyword try...
df.filterFoo(FooColumnName = "foo", Foo = competitors)
}
// apologies, I don't know JUnit at all...
attempt match {
case _: Success =>
failTheTest // should have been a failure
case Failure(ex) => // ex is the thrown exception
// can check that the exception is an IllegalArgumentException or whatever
doChecksOnException
}
}
How it works is that a Try[T] is either a Success wrapping a T or a Failure wrapping a Throwable. Try { } wraps the code in a try block and catches the exception; if it's a NonFatal exception, that exception goes into the Failure. There are some other good reasons to use Try (it has some nicely compositional properties), but those are out of the scope of this answer.

How to handle error scenerios using for comprehensions and error recovery

Currently my error handling is not working as I want to, this is what I am trying to do:
UserApi.insert fails, return its error and don't continue
WorkApi.insert fails, return its error after calling UserApi.delete
WorkApi.assign fails, return its error after calling WorkApi.delete and UserApi.delete
So in summary, UserApi.insert is called, if it is successfull, continue to #2. If WorkApi.insert is successfull, continue. And so on, if the current step fails, you have to reverse the previous one.
Also it is important to return the most relevant error for the Api call that failed.
If all calls were successful, I want to return the first calls value which is a User.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Success, Failure}
val u1 = User("u1")
val w1 = Work("w1")
val resp = for {
insertResp <- UserApi.insert(u1)
workInsertResp <- WorkApi.insert(w1)
workAssignResp <- WorkApi.assign(w1)
} yield insertResp
println("ending...")
resp onComplete {
case Success(r) => println(r)
case Failure(t) => println(t)
}
case class User(name: String)
case class Work(name: String)
case class MyError(name: String)
object UserApi {
def insert(user: User): Future[Either[MyError, User]] =
if (user.name == "u1") Future(Right(user))
else Future(Left(MyError("UserApi.insert")))
def delete(user: User): Future[Either[MyError, String]] =
Future(Right("UserApi.delete"))
}
object WorkApi {
def insert(work: Work): Future[Either[MyError, Work]] =
if (work.name == "w1") Future(Right(work))
else Future(Left(MyError("WorkApi.insert")))
def delete(work: Work): Future[Either[MyError, Work]] = Future(Right(work))
def assign(work: Work): Future[Either[MyError, Work]] =
if (work.name == "w1") Future(Right(work))
else Future(Left(MyError("WorkApi.assign")))
}
Currently I am not sure how to bubble the correct error up.
Note: I am using scala 2.13.x, and I am not using other frameworks just plain Scala.
https://scastie.scala-lang.org/OV4Ax58qQ1S3R3fFUikSbw
I believe this does what you've described.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val resp: Future[Either[MyError,User]] =
UserApi.insert(u1).flatMap{_.fold(
{err => Future.successful(Left(err))}
,usr => WorkApi.insert(w1).flatMap{_.fold(
{err => UserApi.delete(u1); Future.successful(Left(err))}
, _ => WorkApi.assign(w1).map{_.fold(
{err => WorkApi.delete(w1); UserApi.delete(u1); Left(err)}
, _ => Right(usr)
)}
)}
)}
. . . //and the rest of your code
testing:
import scala.concurrent.duration.DurationInt
concurrent.Await.result(resp, 9999.millis)
//res0: Either[MyError,User] = Right(User(u1))
As you can see, your current code design is not well suited for the task you've laid out.
First of all, I would recommend against mixing Future and Either this way. Future has its own way of representing failures, and wrapping an Either in a Future means you will need to handle both the failed Future case and the Left case of Either, which can lead to some confusing code.
In the code provided in the question, there's no asynchronous execution, so using Future is redundant, and you could use Either types directly. However, I assume you want to replace these methods with ones that make actual (asynchronous) API calls, in which case you'll want to use Future without Either. Future requires that failure values extend Throwable, so this would require a change to MyError:
case class MyError(name: String) extends Exception(name)
Second, it's not a good practice to use Future.apply for non-blocking construction as in Future(Right(user)) or Future(Left(MyError("UserApi.insert"))). It isn't obvious, but this actually causes Right(user) to be scheduled as a task on the implicit execution context, rather than being computed synchronously on the current thread. It's better to use Future.successful or Future.failed to create a completed Future when the result is trivial.
With these changes, the new method implementations are:
object UserApi {
def insert(user: User): Future[User] =
if (user.name == "u1") Future.successful(user)
else Future.failed(MyError("UserApi.insert"))
def delete(user: User): Future[String] =
Future.successful("UserApi.delete")
}
object WorkApi {
def insert(work: Work): Future[Work] =
if (work.name == "w1") Future.successful(work)
else Future.failed(MyError("WorkApi.insert"))
def delete(work: Work): Future[Work] =
Future.successful(work)
def assign(work: Work): Future[Work] =
if (work.name == "w1") Future.successful(work)
else Future.failed(MyError("WorkApi.assign"))
}

scalamock stubbing method with specific parameters fails on null

Hi I want to stub a method with specific parameters and to get the result with a helper method
val myDAOMock = stub[MyDao]
(myDAOMock.getFoos(_:String)).when("a").returns(resHelper("a"))
//btw-is there a way to treat "a" as a parameter to the stubbed method and to the return ?
(myDAOMock.getFoos(_:String)).when("b").returns(resHelper("b"))
def resHelpr(x:String) = x match{
case "a" => Foo("a")
case "b" => Foo("b")
}
but it seems that on my test I can capture only one since the 2nd test fails (regardless to the order that I run the tests )
"A stub test" must{
"return Foo(a)" in{
myDAOMock.getFoos("a")
}
"return Foo(b)" in{
myDAOMock.getFoos("b") //this one will fail on null pointer exception
}
how can I improve my stubbing ?
I refactored your example a bit. I believe your issue is that the stubs for getFoos need to be defined within your tests.
import org.scalamock.scalatest.MockFactory
import org.scalatest._
class TestSpec extends FlatSpec with Matchers with MockFactory {
val myDAOMock = stub[MyDao]
val aFoo = Foo("a")
val bFoo = Foo("b")
def resHelper(x: String): Foo = {
x match {
case "a" => aFoo
case "b" => bFoo
}
}
"A stub test" must "return the correct Foo" in {
(myDAOMock.getFoos(_: String)) when "a" returns resHelper("a")
(myDAOMock.getFoos(_: String)) when "b" returns resHelper("b")
assert(myDAOMock.getFoos("a") === aFoo)
assert(myDAOMock.getFoos("b") === bFoo)
}
}
I think this was an issue in older versions of ScalaMock and should now be fixed in later versions, returning a better message instead of the NPE. The NPE happens as you re-used a mock in two cases.
See http://scalamock.org/user-guide/sharing-scalatest/ how to do that safely.

Why does a Scala for-comprehension have to start with a generator?

According to the Scala Language Specification (ยง6.19), "An enumerator sequence always starts with a generator". Why?
I sometimes find this restriction to be a hindrance when using for-comprehensions with monads, because it means you can't do things like this:
def getFooValue(): Future[Int] = {
for {
manager = Manager.getManager() // could throw an exception
foo <- manager.makeFoo() // method call returns a Future
value = foo.getValue()
} yield value
}
Indeed, scalac rejects this with the error message '<-' expected but '=' found.
If this was valid syntax in Scala, one advantage would be that any exception thrown by Manager.getManager() would be caught by the Future monad used within the for-comprehension, and would cause it to yield a failed Future, which is what I want. The workaround of moving the call to Manager.getManager() outside the for-comprehension doesn't have this advantage:
def getFooValue(): Future[Int] = {
val manager = Manager.getManager()
for {
foo <- manager.makeFoo()
value = foo.getValue()
} yield value
}
In this case, an exception thrown by foo.getValue() will yield a failed Future (which is what I want), but an exception thrown by Manager.getManager() will be thrown back to the caller of getFooValue() (which is not what I want). Other possible ways of handling the exception are more verbose.
I find this restriction especially puzzling because in Haskell's otherwise similar do notation, there is no requirement that a do block should begin with a statement containing <-. Can anyone explain this difference between Scala and Haskell?
Here's a complete working example showing how exceptions are caught by the Future monad in for-comprehensions:
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Try, Success, Failure}
class Foo(val value: Int) {
def getValue(crash: Boolean): Int = {
if (crash) {
throw new Exception("failed to get value")
} else {
value
}
}
}
class Manager {
def makeFoo(crash: Boolean): Future[Foo] = {
if (crash) {
throw new Exception("failed to make Foo")
} else {
Future(new Foo(10))
}
}
}
object Manager {
def getManager(crash: Boolean): Manager = {
if (crash) {
throw new Exception("failed to get manager")
} else {
new Manager()
}
}
}
object Main extends App {
def getFooValue(crashGetManager: Boolean,
crashMakeFoo: Boolean,
crashGetValue: Boolean): Future[Int] = {
for {
manager <- Future(Manager.getManager(crashGetManager))
foo <- manager.makeFoo(crashMakeFoo)
value = foo.getValue(crashGetValue)
} yield value
}
def waitForValue(future: Future[Int]): Unit = {
val result = Try(Await.result(future, Duration("10 seconds")))
result match {
case Success(value) => println(s"Got value: $value")
case Failure(e) => println(s"Got error: $e")
}
}
val future1 = getFooValue(false, false, false)
waitForValue(future1)
val future2 = getFooValue(true, false, false)
waitForValue(future2)
val future3 = getFooValue(false, true, false)
waitForValue(future3)
val future4 = getFooValue(false, false, true)
waitForValue(future4)
}
Here's the output:
Got value: 10
Got error: java.lang.Exception: failed to get manager
Got error: java.lang.Exception: failed to make Foo
Got error: java.lang.Exception: failed to get value
This is a trivial example, but I'm working on a project in which we have a lot of non-trivial code that depends on this behaviour. As far as I understand, this is one of the main advantages of using Future (or Try) as a monad. What I find strange is that I have to write
manager <- Future(Manager.getManager(crashGetManager))
instead of
manager = Manager.getManager(crashGetManager)
(Edited to reflect #RexKerr's point that the monad is doing the work of catching the exceptions.)
for comprehensions do not catch exceptions. Try does, and it has the appropriate methods to participate in for-comprehensions, so you can
for {
manager <- Try { Manager.getManager() }
...
}
But then it's expecting Try all the way down unless you manually or implicitly have a way to switch container types (e.g. something that converts Try to a List).
So I'm not sure your premises are right. Any assignment you made in a for-comprehension can just be made early.
(Also, there is no point doing an assignment inside a for comprehension just to yield that exact value. Just do the computation in the yield block.)
(Also, just to illustrate that multiple types can play a role in for comprehensions so there's not a super-obvious correct answer for how to wrap an early assignment in terms of later types:
// List and Option, via implicit conversion
for {i <- List(1,2,3); j <- Option(i).filter(_ <2)} yield j
// Custom compatible types with map/flatMap
// Use :paste in the REPL to define A and B together
class A[X] { def flatMap[Y](f: X => B[Y]): A[Y] = new A[Y] }
class B[X](x: X) { def map[Y](f: X => Y): B[Y] = new B(f(x)) }
for{ i <- (new A[Int]); j <- (new B(i)) } yield j.toString
Even if you take the first type you still have the problem of whether there is a unique "bind" (way to wrap) and whether to doubly-wrap things that are already the correct type. There could be rules for all these things, but for-comprehensions are already hard enough to learn, no?)
Haskell translates the equivalent of for { manager = Manager.getManager(); ... } to the equivalent of lazy val manager = Manager.getManager(); for { ... }. This seems to work:
scala> lazy val x: Int = throw new Exception("")
x: Int = <lazy>
scala> for { y <- Future(x + 1) } yield y
res8: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise#fedb05d
scala> Try(Await.result(res1, Duration("10 seconds")))
res9: scala.util.Try[Int] = Failure(java.lang.Exception: )
I think the reason this can't be done is because for-loops are syntactic sugar for flatMap and map methods (except if you are using a condition in the for-loop, in that case it's desugared with the method withFilter). When you are storing in a immutable variable, you can't use these methods. That's the reason you would be ok using Try as pointed out by Rex Kerr. In that case, you should be able to use map and flatMap methods.

Mocking default values for return types with mockito + specs2

Given a mock:
val myMock = mock[SomeClass]
I am trying to set it up so that the mock returns default values for various types. E.g. for things that returns String, it would return "".
I discovered RETURNS_SMART_NULLS which looks like it works for basic return types like String. Here is how I am using it in Scala / Specs2:
val myMock = mock[SomeClass].settings(smart = true)
Now to the problem: since I am using Scala, my code / APIs do not return nulls but return Option values. So what I am trying to do is get the mock to default to returning a non null value for Option return types: either None (preferred), or Some[T] where T is the type in the container (if its String, then Some("")).
So for example if SomeClass has an attribute address of type Option[String], how can I configure mockito to return None when myMock.address invoked instead of null. These nulls are causing downstream errors.
Note, its not a viable solution for me to specifically mock the behavior of each of these individual invocations (e.g: myMock.address returns None)
I was able to test this class:
class Timers(i: Int, s: String) {
def returnOption: Option[Int] = Some(i)
def returnString: String = s
}
with this test:
import org.specs2.mutable.Specification
import org.junit.runner.RunWith
import org.specs2.runner.JUnitRunner
import org.mockito.Mockito._
import org.mockito.stubbing.Answer
import org.mockito.invocation.InvocationOnMock
#RunWith(classOf[JUnitRunner])
class TimersSpec extends Specification {
val m = mock(classOf[Timers], new Answer[Any]() {
def answer(inv: InvocationOnMock) = {
inv.getMethod.getReturnType match {
case c if c == classOf[Option[_]] => None
case c if c == classOf[String] => ""
case _ => null
}
}
})
"A mock timer" should {
"return a None" in {
m.returnOption must beNone
}
"return a empty string" in {
m.returnString mustEqual ""
}
}
}