Skip to content

Commit

Permalink
Merge pull request #214 from alejandrohdezma/munit-fixtures
Browse files Browse the repository at this point in the history
Create alternative way to use testcontainers in MUnit using fixtures
  • Loading branch information
dimafeng authored Mar 15, 2022
2 parents dd25d2d + 959f8ee commit 76a6bf1
Show file tree
Hide file tree
Showing 5 changed files with 482 additions and 0 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,12 @@ Similarly to Scalatest, you can use one of the four traits:
3. `TestContainersForAll` — will start multiple containers before all tests and stop after all tests.
4. `TestContainersForEach` — will start multiple containers before each test and stop after each test.

Additionally, you have available MUnit fixtures integrations under `TestContainersFixtures`:

1. `ForAllContainerFixture` — will start a single container before all tests and stop after all tests.
2. `ForEachContainerFixture` — will start a single container before each test and stop after each test.
3. `ContainerFunFixture` — will start a single container before each test and stop after each test.

#### Single container in tests

If you want to use a single container for all tests in your suite:
Expand Down Expand Up @@ -613,6 +619,40 @@ class MySpec extends AnyFlatSpec with TestContainersForAll {
}
```

#### Using fixtures

Instead of the `*ForAll`/`*ForEach` traits you can use the fixtures under `TestContainersFixture`:

```scala
class MysqlSpec extends FunSuite with TestContainersFixtures {

// Use `ForAllContainerFixture` to start/stop container before/after all tests
val mysql = ForEachContainerFixture(MySQLContainer())

// You need to override `munitFixtures` and pass in your container fixture
override def munitFixtures = List(mysql)

test("test case name") {
// Inside your test body you can do with your container whatever you want to
assert(mysql().jdbcUrl.nonEmpty)
}
}
```

There is also available a `FunFixture` version for containers:

```scala
class MysqlSpec extends FunSuite with TestContainersFixtures {

val mysql = ContainerFunFixture(MySQLContainer())

mysql.test("test case name") { container =>
// Inside your test body you can do with your container whatever you want to
assert(container.jdbcUrl.nonEmpty)
}
}
```

#### Notes on MUnit usage
- If you use `*ForAll` trait and override beforeAll() without calling super.beforeAll() your containers won't start.
- If you use `*ForAll` trait and override afterAll() without calling super.afterAll() your containers won't stop.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package com.dimafeng.testcontainers.munit.fixtures

import com.dimafeng.testcontainers.lifecycle.Stoppable
import org.testcontainers.lifecycle.Startable
import munit.Suite
import munit.FunFixtures
import munit.FunSuite
import com.dimafeng.testcontainers.munit.TestContainersSuite
import com.dimafeng.testcontainers.lifecycle.TestLifecycleAware

trait TestContainersFixtures { self: FunSuite =>

/**
* Creates a fixture that starts a single container before each test and stops
* it after each test.
*
* @example
* {{{
* class MysqlSpec extends FunSuite with TestContainersFixtures {
*
* val mysql = ForEachContainerFixture(MySQLContainer())
*
* // You need to override `munitFixtures` and pass in your container fixture
* override def munitFixtures = List(mysql)
*
* test("test case name") {
* // Inside your test body you can do with your container whatever you want to
* assert(mysql().jdbcUrl.nonEmpty)
* }
* }
* }}}
*/
class ForEachContainerFixture[T <: Startable with Stoppable](val container: T)
extends Fixture[T]("ForEachTestContainers") {

def apply(): T = container

def afterContainerStart(container: T, context: BeforeEach): Unit = ()

def beforeContainerStop(container: T, context: AfterEach): Unit = ()

override def beforeEach(context: BeforeEach): Unit = {
container.start()
afterContainerStart(container, context)
container match {
case container: TestLifecycleAware => container.beforeTest(suiteDescription)
case _ => // do nothing
}
}

override def afterEach(context: AfterEach): Unit = {
container match {
case container: TestLifecycleAware => container.afterTest(suiteDescription, None)
case _ => // do nothing
}
beforeContainerStop(container, context)
container.stop()
}

}

object ForEachContainerFixture {

/**
* Creates a fixture that starts a single container before each test and stops
* it after each test.
*
* @example
* {{{
* class MysqlSpec extends FunSuite with TestContainersFixtures {
*
* val mysql = ForEachContainerFixture(MySQLContainer())
*
* // You need to override `munitFixtures` and pass in your container fixture
* override def munitFixtures = List(mysql)
*
* test("test case name") {
* // Inside your test body you can do with your container whatever you want to
* assert(mysql().jdbcUrl.nonEmpty)
* }
* }
* }}}
*/
def apply[T <: Startable with Stoppable](container: T) = new ForEachContainerFixture[T](container)

}

/**
* Creates a fixture that starts a single container before all test and stops
* it after all test.
*
* @example
* {{{
* class MysqlSpec extends FunSuite with TestContainersFixtures {
*
* val mysql = ForAllContainerFixture(MySQLContainer())
*
* // You need to override `munitFixtures` and pass in your container fixture
* override def munitFixtures = List(mysql)
*
* test("test case name") {
* // Inside your test body you can do with your container whatever you want to
* assert(mysql().jdbcUrl.nonEmpty)
* }
* }
* }}}
*/
class ForAllContainerFixture[T <: Startable with Stoppable](val container: T)
extends Fixture[T]("ForAllTestContainers") {

def apply(): T = container

def afterContainerStart(container: T): Unit = ()

def beforeContainerStop(container: T): Unit = ()

override def beforeAll(): Unit = {
container.start()
afterContainerStart(container)
}

override def beforeEach(context: BeforeEach): Unit = container match {
case container: TestLifecycleAware => container.beforeTest(suiteDescription)
case _ => // do nothing
}

override def afterAll(): Unit = {
beforeContainerStop(container)
container.stop()
}

override def afterEach(context: AfterEach): Unit = container match {
case container: TestLifecycleAware => container.afterTest(suiteDescription, None)
case _ => // do nothing
}

}

object ForAllContainerFixture {

/**
* Creates a fixture that starts a single container before all test and stops
* it after all test.
*
* @example
* {{{
* class MysqlSpec extends FunSuite with TestContainersFixtures {
*
* val mysql = ForAllContainerFixture(MySQLContainer())
*
* // You need to override `munitFixtures` and pass in your container fixture
* override def munitFixtures = List(mysql)
*
* test("test case name") {
* // Inside your test body you can do with your container whatever you want to
* assert(mysql().jdbcUrl.nonEmpty)
* }
* }
* }}}
*/
def apply[T <: Startable with Stoppable](container: T) = new ForAllContainerFixture[T](container)

}

object ContainerFunFixture {

/**
* Creates a fun-fixture that starts a single container before each test and stops
* it after each test.
*
* @example
* {{{
* class MysqlSpec extends FunSuite with TestContainersFixtures {
*
* val mysql = ContainerFunFixture(MySQLContainer())
*
* mysql.test("test case name") { container =>
* // Inside your test body you can do with your container whatever you want to
* assert(container.jdbcUrl.nonEmpty)
* }
* }
* }}}
*/
def apply[T <: Startable with Stoppable](container: T) = FunFixture[T](
setup = { _ =>
container.start();
container match {
case tla: TestLifecycleAware => tla.beforeTest(suiteDescription)
case _ => // do nothing
}
container
}, teardown = { c =>
c match {
case tla: TestLifecycleAware => tla.afterTest(suiteDescription, None)
case _ => // do nothing
}
c.stop()
}
)

}

private val suiteDescription = TestContainersSuite.createDescription(self)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.dimafeng.testcontainers.munit.fixtures

import java.util.Optional

import com.dimafeng.testcontainers.ContainerDef
import com.dimafeng.testcontainers.munit.SampleContainer
import com.dimafeng.testcontainers.munit.SampleContainer.SampleJavaContainer
import com.dimafeng.testcontainers.munit.fixtures.ContainerFunFixtureSpec._
import munit.{FunSuite, MUnitRunner, Suite}
import org.junit.runner.notification.RunNotifier
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.{times, verify}
import org.mockito.{ArgumentMatchers, Mockito}
import org.scalatestplus.mockito.MockitoSugar
import com.dimafeng.testcontainers.Container

class ContainerFunFixtureSpec extends FunSuite with MockitoSugar {
test("call all appropriate methods of the containers") {
val container = mock[SampleJavaContainer]

val spec = new TestSpec(assert(cond = true), SampleContainer(container))
run(spec)

verify(container).beforeTest(any())
verify(container).start()
verify(container).afterTest(any(), ArgumentMatchers.eq(Optional.empty()))
verify(container).stop()
}

test("call all appropriate methods of the containers if assertion fails") {
val container = mock[SampleJavaContainer]

val spec = new TestSpec(assert(cond = false), SampleContainer(container))

run(spec)

verify(container).beforeTest(any())
verify(container).start()
verify(container).afterTest(any(), ArgumentMatchers.eq(Optional.empty()))
verify(container).stop()
}

test("start and stop containers before and after each test case") {
val container = mock[SampleJavaContainer]

val spec = new MultipleTestsSpec(assert(cond = true), SampleContainer(container))
run(spec)

verify(container, times(2)).beforeTest(any())
verify(container, times(2)).start()
verify(container, times(2)).afterTest(any(), any())
verify(container, times(2)).stop()
}

}

object ContainerFunFixtureSpec {
protected abstract class ContainerSpec extends FunSuite with TestContainersFixtures

protected class TestSpec(testBody: => Unit, container: Container) extends ContainerSpec {
val containerFixture = ContainerFunFixture(container)

containerFixture.test("test") { _ => testBody}
}

protected class MultipleTestsSpec(testBody: => Unit, container: Container) extends ContainerSpec {
val containerFixture = ContainerFunFixture(container)

containerFixture.test("test1") { _ => testBody}
containerFixture.test("test2") { _ => testBody}
}

private def run(res: Suite): Unit = {
val notifier = new RunNotifier()
new MUnitRunner(res.asInstanceOf[Suite].getClass, () => res).run(notifier)
}
}
Loading

0 comments on commit 76a6bf1

Please sign in to comment.