使用Testcontainers测试Doobie解释器时数据插入断言失败求助
问题分析与解决方案
我一眼就看出问题所在啦——你每次调用transactor.use的时候,都会重新创建一个全新的PostgreSQL容器和数据库连接池!
问题根源
你的transactor是一个Resource[IO, Transactor[IO]],而Resource的特性是每次调用use时,都会从头执行整个资源分配流程:
- 启动一个全新的PostgreSQL容器
- 创建新的连接池
- 执行你的数据库操作
- 关闭连接池并停止容器
这意味着:
- 插入操作是在第一个临时容器里完成的,操作结束后容器就被销毁了
- 查询操作又启动了第二个全新的容器,这个容器里根本没有你刚才插入的数据,自然返回
None
解决方案:共享容器与连接池
我们需要让整个测试类共享同一个PostgreSQL容器和连接池,而不是每次操作都重新创建。这里用BeforeAndAfter来初始化和清理资源是最直接的方式:
class CompaniesSpec extends AsyncFreeSpec with AsyncIOSpec with BeforeAndAfter with Matchers { // 声明共享的连接池和容器引用 private var sharedXa: Transactor[IO] = _ private var postgresContainer: PostgreSQLContainer[Nothing] = _ // 测试前初始化一次资源 before { val initResources = for { // 启动Postgres容器并执行初始化脚本 container <- IO { val c = new PostgreSQLContainer().withInitScript("sql/companies.sql") c.start() c } // 创建固定大小的线程池 ce <- ExecutionContexts.fixedThreadPool[IO](1) // 初始化连接池 transactor <- HikariTransactor.newHikariTransactor[IO]( "org.postgresql.Driver", container.getJdbcUrl, container.getUsername, container.getPassword, ce ) } yield (container, transactor) // 同步执行初始化(因为before是同步方法) val (container, xa) = initResources.unsafeRunSync() postgresContainer = container sharedXa = xa } // 测试后清理资源 after { // 关闭连接池和容器 sharedXa.close().unsafeRunSync() postgresContainer.stop() } "Companies algebra " - { "should create a new Company" in { (for { // 使用共享的连接池执行插入 companyId <- sql"INSERT INTO companies (name, description) VALUES ('My Company', 'My Company Description')" .update .withUniqueGeneratedKeys[Int]("id") .transact(sharedXa) // 用同一个连接池查询数据 maybeCompany <- sql"SELECT * FROM companies WHERE id = $companyId" .query[Company] .option .transact(sharedXa) } yield maybeCompany) .asserting { company => company shouldBe defined company.get.name shouldBe "My Company" company.get.description shouldBe "My Company Description" } } } }
更优雅的优化建议
如果想更贴合Cats Effect的资源管理风格,可以使用ScalaTest的资源Fixture特性来管理整个测试的资源生命周期,避免手动处理unsafeRunSync():
import org.scalatest.freespec.AsyncFreeSpec import org.scalatest.matchers.should.Matchers import org.typelevel.cats.effect.testing.scalatest.AsyncIOSpec import cats.effect.{IO, Resource} import org.testcontainers.containers.PostgreSQLContainer class CompaniesSpec extends AsyncFreeSpec with AsyncIOSpec with Matchers { // 定义共享资源:Postgres容器 + 连接池 private val sharedResources: Resource[IO, Transactor[IO]] = for { container <- Resource.make(IO { val c = new PostgreSQLContainer().withInitScript("sql/companies.sql") c.start() c })(c => IO(c.stop())) ce <- ExecutionContexts.fixedThreadPool[IO](1) xa <- HikariTransactor.newHikariTransactor[IO]( "org.postgresql.Driver", container.getJdbcUrl, container.getUsername, container.getPassword, ce ) } yield xa // 使用Resource包裹测试,确保全程复用同一资源 "Companies algebra " - { "should create a new Company" in sharedResources.use { xa => (for { companyId <- sql"INSERT INTO companies (name, description) VALUES ('My Company', 'My Company Description')" .update .withUniqueGeneratedKeys[Int]("id") .transact(xa) maybeCompany <- sql"SELECT * FROM companies WHERE id = $companyId" .query[Company] .option .transact(xa) } yield maybeCompany) .asserting { company => company shouldBe defined company.get.name shouldBe "My Company" company.get.description shouldBe "My Company Description" } } } }
这种方式下,sharedResources只会初始化一次,整个测试都复用同一个容器和连接池,测试结束后自动释放资源,完全符合Cats Effect的资源管理理念。
内容的提问来源于stack exchange,提问作者riccardo.cardin
相关产品推荐
相关产品推荐

