You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Scalatest异步测试中如何实现多条件断言并快速失败?

解决Scalatest异步测试的编译问题与需求实现

问题原因

原代码中map块的最后一个表达式是SomeGlobalContextHolder.ctx.foreach(...),而foreach返回Unit,导致整个map的结果类型是Unit,最终返回Future[Unit],但Scalatest的AsyncFlatSpec要求测试用例必须返回Future[Assertion],因此出现编译错误。

解决方案

以下两种方案均能满足你的三个需求:编译通过、条件执行断言、快速失败。

方案1:直接执行断言并返回succeed

这种方案简洁直观,利用Scalatest断言失败即抛出异常的特性实现快速失败:

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 shouldBe "some result"
      
      // 条件断言:仅当ctx存在时执行
      SomeGlobalContextHolder.ctx.foreach { ctx =>
        // 仅当ssoId存在时执行该断言
        user.ssoId.foreach { ssoId =>
          ctx("ssoId") should contain(ssoId)
        }
        // 无论ssoId是否存在,都执行该断言
        ctx("user") should contain(user.id)
      }
      
      // 返回Assertion,满足编译要求
      succeed
    }
  }
}

方案2:组合所有断言为一个Assertion

这种方案将所有断言(包括条件断言)组合成一个Assertion,更符合Scalatest的断言组合风格:

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 =>
      // 基础断言
      val baseAssertion = result shouldBe "some result"
      
      // 组合条件断言
      val conditionalAssertions = SomeGlobalContextHolder.ctx.fold(succeed) { ctx =>
        // ssoId存在时执行断言,否则返回succeed(无断言)
        val ssoAssertion = user.ssoId.fold(succeed)(ssoId => ctx("ssoId") should contain(ssoId))
        // 必执行的断言
        val userAssertion = ctx("user") should contain(user.id)
        // 组合两个断言
        ssoAssertion and userAssertion
      }
      
      // 组合所有断言,返回最终的Assertion
      baseAssertion and conditionalAssertions
    }
  }
}

需求满足说明

  1. 编译通过:两种方案的map块最终都返回Assertion类型,因此Future的类型为Future[Assertion],符合Scalatest的要求。
  2. 条件执行断言:通过Option.foreach(方案1)或Option.fold(方案2)实现仅当Option有值时执行对应断言。
  3. 快速失败:Scalatest的断言失败会立即抛出TestFailedException,导致Future进入失败状态,测试会立即终止,不会执行后续断言。

内容的提问来源于stack exchange,提问作者danissimo

火山引擎 最新活动