Search code examples
scalahttp4scats-effect

Integration tests for Http4s Client/Resource


I'm implementing a Vault client in Scala using Http4s client.

And I'm now starting to write integration tests. So far I have this:

abstract class Utils extends AsyncWordSpec with Matchers {
  implicit override def executionContext = ExecutionContext.global
  implicit val timer: Timer[IO] = IO.timer(executionContext)
  implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)

  val vaultUri = Uri.unsafeFromString(Properties.envOrElse("VAULT_ADDR", throw IllegalArgumentException))
  val vaultToken = Properties.envOrElse("VAULT_TOKEN", throw IllegalArgumentException)
  val clientResource = BlazeClientBuilder[IO](global)
    .withCheckEndpointAuthentication(false)
    .resource

  def usingClient[T](f: VaultClient[IO] => IO[Assertion]): Future[Assertion] = {
    clientResource.use { implicit client =>
      f(new VaultClient[IO](vaultUri, vaultToken))
    }.unsafeToFuture()
  }
}

Then my tests look like this (just showing one test):

class SysSpec extends Utils {
  "The auth endpoint" should {
    "successfully mount an authentication method" in {
      usingClient { client =>
        for {
          result <- client.sys.auth.create("test", AuthMethod(
            "approle", "some nice description", config = TuneOptions(defaultLeaseTtl = 60.minutes)
          ))
        } yield result should be (())
      }
    }
  }
}

This approach works, however it doesn't feel right. For each test I'm opening the connection (clientResource.use) and recreating the VaultClient.

Is there a way for me to reuse the same connection and client for all the tests in SysSpec.

Please note these are integration tests and not unit tests.


Solution

  • This is the best I could come up with.

    abstract class Utils extends AsyncWordSpec with Matchers with BeforeAndAfterAll {
      implicit override def executionContext = ExecutionContext.global
      implicit val timer: Timer[IO] = IO.timer(executionContext)
      implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)
    
      val (httpClient, finalizer) = BlazeClientBuilder[IO](global)
        .withCheckEndpointAuthentication(false)
        .resource.allocated.unsafeRunSync()
      override protected def afterAll(): Unit = finalizer.unsafeRunSync()
    
      private implicit val c = httpClient
      val client = new VaultClient[IO](uri"http://[::1]:8200", "the root token fetched from somewhere")
    }
    

    Then the tests just use the client directly:

    class SysSpec extends Utils {
      "The auth endpoint" should {
        "successfully mount an authentication method" in {
          client.sys.auth.create("test", AuthMethod(
            "approle", "some nice description",
            config = TuneOptions(defaultLeaseTtl = 60.minutes))
          ).map(_ shouldBe ()).unsafeToFuture()
        }
      }
    }
    

    My two main problems with this approach are the two unsafeRunSyncs in the code. The first one is to create the client and the second one to clean the resource. However it is a much better approach then repeatedly creating and destroy the client.

    I would also like not to use the unsafeToFuture but that would require ScalaTest to support Cats-Effect directly.