0

First, I need this async test to get compiled.
Second, some assertions are made conditionally, depending on whether options are defined, while others are not.
Third, I'd like the test to fail fast, that is, on the first failed assertion.

import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers._
import scala.concurrent.Future

class MultipleOptionAsyncTests extends AsyncFlatSpec {
  object SomeGlobalContextHolder {
    val ctx: Option[SomeGlobalContext] = Some(new SomeGlobalContext)
  }
  class SomeGlobalContext {
    def apply(key: String): Set[String] = Set("values that have been set for the key")
  }

  case class User (id: String, ssoId: Option[String])
  type TypeOfResult = String
  def methodBeingTested(user: User): Future[TypeOfResult] = Future.successful("some result")

  it should "get compiled and test ALL assertions" in {
    val user = User("id", Some("ssoId"))
    methodBeingTested(user).map { result =>
      result should be "some result"
      SomeGlobalContextHolder.ctx.foreach { ctx =>
        user.ssoId.foreach { ssoId =>
          ctx("ssoId") should contain (ssoId) // fail the entire test if this assertion fails
        }
        ctx("user") should contain (user.id) // fail the entire test if this assertion fails
      }
    }
  } // expected Future[Assertion], but facing Future[Unit]
}

2 Answers 2

3

The compilation is failing because the signature of in from AsyncFlatSpec is

def in(testFun: => Future[compatible.Assertion])

This means that the parameter must be a function of type Unit => Future[Assertion]. In scala, the last line es the value returned.

From your example

  it should "get compiled and test ALL assertions" in {
    val user = User("id", Some("ssoId"))
    methodBeingTested(user).map { result => // this `map` returns a new future
      result should be "some result"
      SomeGlobalContextHolder.ctx.foreach { ctx => // `Option.foreach` returns a `Unit`
        user.ssoId.foreach { ssoId =>
          ctx("ssoId") should contain (ssoId)
        }
        ctx("user") should contain (user.id)
      }
    } // this is your last line that returns the result of `Option.foreach`
  }

Option has the method foreach that lets you pass a function to be executed only if the Option is Some. Which means, you can pass the assertions as parameter to the foreach and they will only be validated when Option is Some

So, you could rewrite your test to something like

import org.scalatest.OptionValues
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.concurrent.Future

class MultipleOptionAsyncTests extends AsyncFlatSpec with Matchers with OptionValues {
  object SomeGlobalContextHolder {
    val ctx: Option[SomeGlobalContext] = Some(new SomeGlobalContext)
  }
  class SomeGlobalContext {
    def apply(key: String): Set[String] = Set("ssoId", "id")
  }

  case class User (id: String, ssoId: Option[String])
  type TypeOfResult = String
  def methodBeingTested(user: User): Future[TypeOfResult] = Future.successful("some result")

  it should "get compiled and test ALL assertions" in {
    val user = User("id", Some("asdf"))
    methodBeingTested(user).map { result =>
      result should be("some result")
      SomeGlobalContextHolder
        .ctx
        // assert when `Option` is `Some`
        .foreach(ctx => ctx("user") should contain (user.id)) 
      
      for {
        // if some of the following `Option`s is `None`,
        // no assertion will be executed
        ssoId <- user.ssoId
        ctx <- SomeGlobalContextHolder.ctx
      } {
        ctx("ssoId") should contain (ssoId)
      }
      succeed
    }
  }
}
Sign up to request clarification or add additional context in comments.

Comments

0

My mate suggested several approaches, and I came up with the following:

it should "get compiled and test ALL assertions" in {
  val user = User("id", Some("ssoId"))
  methodBeingTested(user).map { result =>
    def assertFor[T](opt: Option[T]) = opt.fold(succeed) _
    result should be "some result"
    assertFor(SomeGlobalContextHolder.ctx) { ctx =>
      ctx("user") should contain (user.id)
      assertFor(user.ssoId) { ssoId =>
        ctx("ssoId") should contain (ssoId)
      }
    }
  }
}

Alternatively, you might consider using the pure fold.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.