Search code examples
scalaunit-testingscalatestscalamock

scalatest - test a method of Future[S] with fallbackTo


Premise: When my API responds to a request for the User object, I want to try enriching it with the properties of case class PartnerView(id: String, vipStatus: Option[Boolean], latestSession: Option[Timestamp]. Since the database can be unreliable at times, I use fallbackTo to provide the values as optional, thus not displaying them in the User JSON response.

The following implementation seems to work so far (running the request through Postman returns the User JSON without the optional values) yet my unit test would complain as if I had an uncaught Exception.

The Service class:

class Service(repo: Repository) {
  def get(id: String): Future[Partner] = {
    val account = repo.getAccount(id)
    val getLatestSession = repo.getLatestSession(id)

    val partnerView = (for {
      account <- getAccount
      latestStartTime <- getLatestSession.map {
        case Some(x) => x.scheduledStartTime
        case _ => None
      }
    } yield PartnerView(partnerId, account.vipStatus, latestStartTime))
      .fallbackTo(Future.successful(PartnerView(id, None, None)))

    partnerView
  }
}

The Repository class:

class Repository(database: DatabaseDef, logger: LoggingAdapter) {
  def getAccount(id: String): Future[Account] = database.run((...).result.head)
    .recover {
      case e: Exception =>
        logger.error(e, "DB Server went down")
        throw e
    }

  def getLatestSession(id: String): Future[Option[Session]] = database.run((...).result.headOption)
    .recover {
      case e: Exception =>
        logger.error(e, "DB Server went down")
        throw e
    }
}

The Unit Test:

class ServiceSpec extends AsyncFlatSpec with AsyncMockFactory with OneInstancePerTest {
  val mockRepo = mock[Repository]
  val service = new Service(mockRepo)

  behaviour of "Service"

  it should "get an empty PartnerView when the repository get an Exception" in {
    (mockRepository.getAccount _)
      .expects("partner")
      .throwing(new Exception)

    service.get("partner")
      .map(partnerView => assert(partnerView.id == "partner" && partnerView.vipStatus.isEmpty))
  }
}

The test would fail with the message

Testing started at 5:15 p.m. ...









java.lang.Exception was thrown.
{stacktrace here}

I'm expecting the Exception to


Solution

  • By changing the mock setup to below, the test ran successfully:

      it should "get an empty PartnerView when the repository get an Exception" in {
        (mockRepository.getAccount _)
          .expects("partner")
          .returning(Future.failed(new Exception))
    
        ...
      }
    

    since the recover method wraps the Exception inside a Future

    Sources:

    recover vs recoverWith

    official scala article