Slick TestKit

When you write your own database profile for Slick, you need a way to run all the standard unit tests on it (in addition to any custom tests you may want to add) to ensure that it works correctly and does not claim to support any capabilities which are not actually implemented. For this purpose the Slick unit tests have been factored out into a separate Slick TestKit project.

To get started, you can download the TestKit Example project which contains a copy of Slick’s standard PostgreSQL profile and all the infrastructure required to build and test it.

Scaffolding

Its build.sbt file is straight-forward. It adds the dependencies for Slick, TestKit, junit-interface, Logback and the PostgreSQL JDBC driver, and it sets some options for the test runs:

sourceimport _root_.io.github.nafg.mergify.dsl.*

ThisBuild / scalaVersion := "2.13.15"
ThisBuild / scalacOptions += "-Xsource:3"

mergifyExtraConditions := Seq(
  (Attr.Author :== "scala-steward") ||
    (Attr.Author :== "slick-scala-steward[bot]") ||
    (Attr.Author :== "renovate[bot]")
)

libraryDependencies ++= List(
  "com.github.sbt" % "junit-interface" % "0.13.3" % Test,
  "ch.qos.logback" % "logback-classic" % "1.5.10" % Test,
  "org.postgresql" % "postgresql"      % "42.7.4" % Test
)

scalacOptions += "-deprecation"

Test / parallelExecution := false

logBuffered := false

run / fork := true

testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-v", "-s", "-a")
libraryDependencies += "com.typesafe.slick" %% "slick-testkit" % "3.5.2"
libraryDependencies += "org.scala-lang"      % "scala-reflect" % scalaVersion.value

ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11"))
ThisBuild / githubWorkflowBuildPreamble +=
  WorkflowStep.Run(List("docker compose up -d"), name = Some("Start database"))

There is a copy of Slick’s logback configuration in src/test/resources/logback-test.xml but you can swap out the logging framework if you prefer a different one.

Profile

The actual profile implementation can be found under src/main/scala:

sourcepackage slick.examples.testkit

trait MyPostgresProfile
    extends JdbcProfile
    with JdbcActionComponent.MultipleRowsPerStatementSupport {
  // ...
}

object MyPostgresProfile extends MyPostgresProfile

Test Harness

In order to run the TestKit tests, you need to add a class that extends ProfileTest, plus an implementation of TestDB which tells TestKit how to connect to a test database, get a list of tables, clean up between tests, etc.

In the case of the PostgreSQL test harness (in src/test/scala/MyPostgresTest.scala) most of the default implementations can be used out of the box. Only localTables and getLocalSequences require custom implementations. We also modify the profile’s capabilities to indicate that our profile does not support the JDBC getFunctions call:

source@RunWith(classOf[Testkit])
class MyPostgresTest extends ProfileTest(MyPostgresTest.tdb)

object MyPostgresTest {
  def tdb = new ExternalJdbcTestDB("mypostgres") {
    val profile: MyPostgresProfile.type = MyPostgresProfile
    override def localTables(implicit
        ec: ExecutionContext
    ): DBIO[Vector[String]] =
      ResultSetAction[(String, String, String, String)](
        _.conn.getMetaData.getTables("", "public", null, null)
      ).map { ts =>
        ts.filter(_._4.toUpperCase == "TABLE").map(_._3).sorted
      }
    override def localSequences(implicit
        ec: ExecutionContext
    ): DBIO[Vector[String]] =
      ResultSetAction[(String, String, String, String)](
        _.conn.getMetaData.getTables("", "public", null, null)
      ).map { ts =>
        ts.filter(_._4.toUpperCase == "SEQUENCE").map(_._3).sorted
      }
    override def capabilities           =
      super.capabilities - TestDB.capabilities.jdbcMetaGetFunctions
  }
}

The name of a configuration prefix, in this case mypostgres, is passed to ExternalJdbcTestDB:

sourcedef tdb = new ExternalJdbcTestDB("mypostgres") {

Database Configuration

Since the PostgreSQL test harness is based on ExternalJdbcTestDB, it needs to be configured in test-dbs/testkit.conf:

source# Enable the tests for mypostgres:
mypostgres.enabled = true

# Set the user name (default: "postgres")
mypostgres.user = "postgres"

# Set the password (default: no password)
mypostgres.password = postgres

mypostgres.create = CREATE DATABASE ${testDB} "TEMPLATE = template0"

mypostgres.baseURL = "jdbc:postgresql://localhost:54325/"

There are several other configuration options that need to be set for an ExternalJdbcTestDB. These are defined with suitable defaults in testkit-reference.conf so that testkit.conf can be kept very simple in most cases:

sourcemypostgres {
  baseURL = "jdbc:postgresql:"
  user = postgres
  adminDB = postgres
  create = [
    CREATE TABLESPACE slick_test LOCATION '${testkit.absTestDir}'
    CREATE DATABASE ${testDB} "TEMPLATE = template0 TABLESPACE slick_test"
  ]
  postCreate = "create extension lo"
  drop = [
    DROP DATABASE IF EXISTS ${testDB}
    DROP TABLESPACE IF EXISTS slick_test
  ]
  driver = org.postgresql.Driver
}

Testing

Running sbt test discovers MyPostgresTest and runs it with TestKit’s JUnit runner. This in turn causes the database to be set up through the test harness and all tests which are applicable for the profile (as determined by the capabilities setting in the test harness) to be run.

The source code for this page can be found here.