Skip to content

Commit

Permalink
Merge pull request #1 from alejandrohdezma/http4s-munit-testcontainers
Browse files Browse the repository at this point in the history
Add integration to test an HTTP server from a docker container
  • Loading branch information
alejandrohdezma authored Dec 14, 2020
2 parents 0fd4cf9 + bef7cd0 commit c290ff8
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 6 deletions.
21 changes: 15 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
ThisBuild / scalaVersion := "2.13.4"
ThisBuild / crossScalaVersions := Seq("2.12.12", "2.13.4")
ThisBuild / organization := "com.alejandrohdezma"
ThisBuild / extraCollaborators += Collaborator.github("gutiory")
ThisBuild / scalaVersion := "2.13.4"
ThisBuild / crossScalaVersions := Seq("2.12.12", "2.13.4")
ThisBuild / organization := "com.alejandrohdezma"
ThisBuild / extraCollaborators += Collaborator.github("gutiory")
ThisBuild / testFrameworks += new TestFramework("munit.Framework")
ThisBuild / Test / parallelExecution := false

addCommandAlias("ci-test", "fix --check; mdoc; +test")
addCommandAlias("ci-docs", "github; headerCreateAll; mdoc")
Expand All @@ -12,7 +14,6 @@ lazy val documentation = project
.settings(mdocOut := file("."))

lazy val `http4s-munit` = module
.settings(testFrameworks += new TestFramework("munit.Framework"))
.settings(libraryDependencies += "org.scalameta" %% "munit" % "0.7.19")
.settings(libraryDependencies += "org.http4s" %% "http4s-async-http-client" % "0.21.14")
.settings(libraryDependencies += "org.http4s" %% "http4s-client" % "0.21.14")
Expand All @@ -21,4 +22,12 @@ lazy val `http4s-munit` = module
.settings(libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" % Test)
.settings(libraryDependencies += "org.http4s" %% "http4s-circe" % "0.21.14" % Test)
.settings(addCompilerPlugin("org.typelevel" % "kind-projector" % "0.11.1" cross CrossVersion.full))
.settings(Test / parallelExecution := false)

lazy val `http4s-munit-testcontainers` = module
.settings(libraryDependencies += "org.scalameta" %% "munit" % "0.7.19")
.settings(libraryDependencies += "com.dimafeng" %% "testcontainers-scala-munit" % "0.38.7")
.settings(libraryDependencies += "org.http4s" %% "http4s-async-http-client" % "0.21.14")
.settings(libraryDependencies += "org.http4s" %% "http4s-client" % "0.21.14")
.settings(libraryDependencies += "org.http4s" %% "http4s-dsl" % "0.21.14")
.settings(libraryDependencies += "org.typelevel" %% "munit-cats-effect-2" % "0.11.0")
.settings(libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" % Test)
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright 2020 Alejandro Hernández <https://github.com/alejandrohdezma>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package munit

import cats.effect.IO
import cats.syntax.all._

import com.dimafeng.testcontainers.munit.TestContainerForAll
import org.http4s.Request
import org.http4s.Response
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.client.asynchttpclient.AsyncHttpClient

/**
* Base class for suites testing HTTP servers running in testcontainers.
*
* The container must expose an HTTP server in the 8080 port.
*
* @author Alejandro Hernández
* @author José Gutiérrez
*/
abstract class HttpFromContainerSuite
extends CatsEffectSuite
with CatsEffectFunFixtures
with TestContainerForAll
with LowPrecedenceContainer2Uri {

/**
* Allows altering the name of the generated tests.
*
* By default it will generate test names like:
*
* {{{
* test(GET(uri"users" / 42)) // GET -> users/42
* test(GET(uri"users")).alias("All users") // GET -> users (All users)
* }}}
*/
def munitHttp4sNameCreator(request: Request[IO], testOptions: TestOptions): String = {
val clue = if (testOptions.name.nonEmpty) s" (${testOptions.name})" else ""

s"${request.method.name} -> ${Uri.decode(request.uri.renderString)}$clue"
}

def httpClient: FunFixture[Client[IO]] = ResourceFixture(AsyncHttpClient.resource[IO]())

case class TestCreator(request: Request[IO], testOptions: TestOptions) {

/** Mark a test case that is expected to fail */
def fail: TestCreator = tag(Fail)

/**
* Mark a test case that has a tendency to non-deterministically fail for known or unknown reasons.
*
* By default, flaky tests fail like basic tests unless the `MUNIT_FLAKY_OK` environment variable is set to `true`.
* You can override [[munitFlakyOK]] to customize when it's OK for flaky tests to fail.
*/
def flaky: TestCreator = tag(Flaky)

/** Skips an individual test case in a test suite */
def ignore: TestCreator = tag(Ignore)

/** When running munit, run only a single test */
def only: TestCreator = tag(Only)

/** Add a tag to this test */
def tag(t: Tag): TestCreator = copy(testOptions = testOptions.tag(t))

/** Adds an alias to this test (the test name will be suffixed with this alias when printed) */
def alias(s: String): TestCreator = copy(testOptions = testOptions.withName(s))

def apply(body: Response[IO] => Any)(implicit loc: munit.Location, container2Uri: Containers => Uri): Unit =
httpClient.test(testOptions.withName(munitHttp4sNameCreator(request, testOptions)).withLocation(loc)) { client =>
withContainers { (container: Containers) =>
val uri = Uri.resolve(container2Uri(container), request.uri)

client.run(request.withUri(uri)).use { response =>
body(response) match {
case io: IO[Any] => io
case a => IO(a)
}
}
}
}

}

/**
* Declares a test for the provided request.
*
* @example
* {{{
* test(GET(uri"users" / 42)) { response =>
* // test body
* }
* }}}
*/
def test(request: IO[Request[IO]]): TestCreator =
TestCreator(request.unsafeRunSync(), new TestOptions("", Set.empty, Location.empty))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package munit

import com.dimafeng.testcontainers.GenericContainer
import org.http4s.Uri

trait LowPrecedenceContainer2Uri {

implicit def GenericContainer2Uri[A <: GenericContainer]: A => Uri = container =>
Uri.unsafeFromString(s"http://localhost:${container.mappedPort(80)}")

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
<logger name="org.http4s" level="off"/>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package munit

import com.dimafeng.testcontainers.GenericContainer
import org.testcontainers.containers.wait.strategy.Wait

final case class DummyHttpContainer(underlying: GenericContainer) extends GenericContainer(underlying)

object DummyHttpContainer {

final case class Def()
extends GenericContainer.Def[DummyHttpContainer](
new DummyHttpContainer(
GenericContainer(
dockerImage = "briceburg/ping-pong",
exposedPorts = Seq(80),
waitStrategy = Wait.forHttp("/ping")
)
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2020 Alejandro Hernández <https://github.com/alejandrohdezma>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package munit

import org.http4s.Method.GET
import org.http4s.client.dsl.io._
import org.http4s.syntax.all._

class HttpFromContainerSuiteSuite extends HttpFromContainerSuite {

override val containerDef = DummyHttpContainer.Def()

test(GET(uri"ping")) { response =>
assertEquals(response.status.code, 200)

assertIO(response.as[String], "pong")
}

}

0 comments on commit c290ff8

Please sign in to comment.