Hi! If I need to configure an instance of a testco...
# kotest
d
Hi! If I need to configure an instance of a testcontainer for postgres, where whould I do that? It seems like
Startable.perProject(...)
takes a name param, but doesn't work for a customized container...
Does it just use the containerName internally in Kotest? And the startable is just any already configured container?
@sam, How would I use the extension for this:
Copy code
class Postgresql: PostgreSQLContainer<Postgresql>("postgres:9.6")  {
	companion object {
		private lateinit var instance: Postgresql
		fun start() {
			if (!Companion::instance.isInitialized) {
				// Reuse container between tests instead of starting a new one per execution
				instance = Postgresql()
				instance
					.withReuse(true)
					.withUsername("...")
					.withDatabaseName("...")
					.withInitScript("db-structure.sql")
					.start()

				System.setProperty("datasources.default.url", instance.jdbcUrl)
				System.setProperty("datasources.default.username", instance.username)
				System.setProperty("datasources.default.password", instance.password)
			}
		}

		fun stop() {
			if (Companion::instance.isInitialized) {
				instance.stop()
			}
		}
	}
}
I don't see any real example in the docs or the tests...
s
d
There it has
GenericContainer<Nothing>("redis:5.0.3-alpine")
... but not using configuration like I did in the previous code.
Dbs are more complicated, and micronaut needs those system props to be able to communicate with the db...
s
All kotest is doing is calling start / stop as part of the test lifecycle, so you should be able to create any container you want ,with any config you want, and then pass it into
listener(mycontainer.perTest())
d
s
what is perProject
d
Copy code
fun <T : Startable> T.perProject(containerName: String): StartablePerProjectListener<T> = StartablePerProjectListener<T>(this, containerName)
In the kotest extenstion lib
s
ok let me check
I'll get it working, you update the docs
deal ? 😛
😁 1
d
It seems like the problem is that Postgresql is a class... if I pass it's
instance
in the companion object, it would be ok... but that's a pretty messy way to configure an extension like this 🤒
s
TestContainers is an amazing project, but it has the weirdiest type variance I've ever seen.
you have to subclass it
so do this
Copy code
class PostgresTest : FunSpec() {
   init {

      class MyPostgreSQLContainer : PostgreSQLContainer<MyPostgreSQLContainer>("somedocker")

      val postgres = MyPostgreSQLContainer()
         .withReuse(true)
         .withUsername("...")
         .withDatabaseName("...")
      
      listener(postgres.perProject("listener-name"))

      test("with postgres container") {

      }
   }
}
Look forward to your pr 😉
d
In the example, you had
GenericContainer<Nothing>(...)
, that can't be used here too?
PostgreSQLContainer<Nothing>("somedocker")
?
s
Not if you want to set config because it uses the type parameter as the return type
Which is why you need to subclass it, because what they've done doesn't work in kotlin
d
Oh... so really to make this cleaner, the only way would be to provide a subclass for all the available types... 🤕. Thanks for the help! I'll have to think out what can be improved in a PR...
s
the docs would be enough 🙂
👍🏼 1
d
What about the listener order? Is there a guarantee that a listener put earlier in the ProjectConfig gets loaded first? I see that in the testcontainers listener, it's using withContext, does that mean they get loaded in parallel?
s
they will run sequentially, in the order they are defined in the test class
ones defined outside the test class may run before or after
d
I mean the ones in the global config?
s
They will run in order but it's undefined if all of those run before all the ones in the test
You could check the source but there's no guarantees it won't move about
d
It might be nice to have some kind of priority setting here, no?
s
yep
If you can think of some suitable syntax please create an issue and we can put it in 4.5
d
Micronaut isn't too forgiving when things don't come up on time... I'll try to give it a thought, thanks!
t
we went for
Copy code
class ProjectConfig : AbstractProjectConfig() {
  override fun listeners() = listOf(
    StartablePerProjectListener(
      SpringPostgreSQLContainer("10").withNetwork(Network.SHARED)
        .withNetworkAliases("db"), "postgres"
    ),
    StartablePerProjectListener(
      SpringLocalstackContainer("0.11.2").withServices(LocalStackContainer.Service.S3)
        .withNetwork(Network.SHARED)
        .withNetworkAliases("s3"), "s3"
    ),
    SpringListener
  )
}

class SpringLocalstackContainer(tag: String) :
  LocalStackContainer(DockerImageName.parse("localstack/localstack").withTag(tag)) {
  override fun start() {
    super.start()
    System.setProperty("S3_ENDPOINT", getEndpointConfiguration(Service.S3).serviceEndpoint)
    System.setProperty("S3_REGION", region)
    System.setProperty("AWS_ACCESS_KEY", accessKey)
    System.setProperty("AWS_SECRET_KEY", secretKey)
  }

}

class SpringPostgreSQLContainer(tag: String) :
  PostgreSQLContainer<SpringPostgreSQLContainer>(DockerImageName.parse(IMAGE).withTag(tag)) {
  override fun start() {
    super.start()
    System.setProperty("DB_URL", "jdbc:postgresql://${connectionString()}")
    System.setProperty("DB_USERNAME", username)
    System.setProperty("DB_PASSWORD", password)
    System.setProperty("spring.flyway.schemas", databaseName)
  }

  private fun connectionString() = "$host:$firstMappedPort/$databaseName"
}
but we have a very simple project with just this 2 containers as dependencies for now got hit by the weird type variance as well: https://github.com/testcontainers/testcontainers-java/issues/318
d
Good point @thanksforallthefish... the system props need to be declared after the container starts... I ended up with altering the startable project listener:
Copy code
class StartablePerProjectListener<T : Startable>(
	val startable: T,
	val containerName: String,
	val afterStart: (T.() -> Unit)?
) : TestListener,
	ProjectListener {
	override val name = containerName
	private val testLifecycleAwareListener = TestLifecycleAwareListener(startable)

	override suspend fun beforeProject() {
		withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
			startable.start()
			afterStart?.invoke(startable)
		}
	}
...
But that's still not so clean...
s
You could have two listeners, one to start the container, and one to configure it, and run them in order.
d
Yeah, but that gets a bit messy when managing multiple containers...
432 Views