One testcontainers for multiple tests

1.9k views Asked by At

I'm on scala, and I have multiple test files for different classes (testsuites), each of them uses testcontainers (init from the same script).

When I launch all tests in the project, all tests failed (problem connection with database due to testContainers).

When I launch separately tests, all the tests success.

Is there a way to launch only one container for multiple test files (testsuites)? TestContainerForAll seems to work only for tests in the same file.


Edit after @Matthias Berndt reply :

Here libs that I'm using :

  • "org.scalatest" %% "scalatest" % "3.0.8"
  • "com.dimafeng" %% "testcontainers-scala-scalatest" % "0.38.1"
  • "com.dimafeng" %% "testcontainers-scala-postgresql" % "0.38.1"

And here my code


trait DAOTest extends ForAllTestContainer {
  this: Suite =>

  override val container: PostgreSQLContainer = PostgreSQLContainer()
  container.container.withInitScript("extractData.sql")

  container.start()
  ConfigFactory.invalidateCaches()
  System.setProperty("jdbc.url", container.jdbcUrl)
  ConfigFactory.invalidateCaches()

}

3

There are 3 answers

3
Vitaly Chura On

One of the options is (in java terms) to make an abstract base class, declare a container as a static variable and extend that class in your tests. In this case container will be created only once when base class is loaded.

0
Matthias Berndt On

Assuming you're using Scalatest, it should be possible to use nested Suites. I'll use MySQL as an example here since that's what testcontainers-scala uses:

class MysqlSpec extends FlatSpec with ForAllTestContainer {

  override val container = MySQLContainer()

  override def nestedSuites = Vector(
    new SomeDatabaseTest(container)
  )
}
class SomeDatabaseTest(container: MySQLContainer) extends FlatSpec {
  it should "do something" in {
    // do stuff with the container
  }
}
0
DenisNovac On

The Weaver test framework has great features to share resources among the test suites (also have features to share resources among test cases). Also there is a great integration with Cats. It is working sort of like DI but for tests.

First you need to register the global resource in object extending the GlobalResource (you also need LowPriorityImplicits if you want to register resources with generics such as Transactor[IO]):

import cats.effect.{IO, Resource}
import com.github.denisnovac.multitest.model.DBConfig
import org.testcontainers.containers.PostgreSQLContainer
import weaver.{GlobalResource, GlobalWrite, LowPriorityImplicits}

object SharedPostgresContainer extends GlobalResource with LowPriorityImplicits {

  private val container = Resource.make(IO(new PostgreSQLContainer("postgres:alpine")).map { c =>
    c.start()
    println(s"Started postgresql container ${c.getJdbcUrl}")
    c
  }) { c =>
    println(s"Closing postgresql container ${c.getJdbcUrl}")
    IO(c.stop())
  }

  override def sharedResources(global: GlobalWrite): Resource[IO, Unit] =
    for {
      c <- container

      // this service returns Transactor[IO]
      xa <- DBService 
              .make[IO]
              .run(
                DBConfig(
                  driver = c.getDriverClassName,
                  url = c.getJdbcUrl,
                  user = c.getUsername,
                  password = c.getPassword
                )
              )
      _  <- global.putR(xa)(classBasedInstance)
    } yield ()

}

And then you use this resource in any test suit class. If you extend the IOSuit trait you can make the suits with sharedResource. Or you can use SimpleIOSuit and get the resource out of global in each suit where you need it.

import cats.effect.{IO, Resource}
import doobie.hikari.HikariTransactor
import weaver.{GlobalRead, IOSuite, LowPriorityImplicits}

import java.time.Instant

class UserRepoSuit(global: GlobalRead) extends IOSuite with LowPriorityImplicits {

  /* This is for sharing the resource among test cases */

  override type Res = HikariTransactor[IO]

  override def sharedResource: Resource[IO, Res] =
    global.getOrFailR[HikariTransactor[IO]](None)(classBasedInstance)

  test("Test with Transactor 1") { xa =>
     expect(true)
  }

  test("Test with Transactor 2") { xa =>
     expect(true)
  }
}