diff --git a/build.sbt b/build.sbt index 51b9249d5..2af6f63f5 100644 --- a/build.sbt +++ b/build.sbt @@ -16,6 +16,7 @@ lazy val postgresVersion = "42.2.20" lazy val refinedVersion = "0.9.25" lazy val scalaCheckVersion = "1.15.4" lazy val scalatestVersion = "3.2.9" +lazy val munitVersion = "0.7.26" lazy val shapelessVersion = "2.3.7" lazy val silencerVersion = "1.7.1" lazy val specs2Version = "4.11.0" @@ -168,6 +169,7 @@ lazy val doobie = project.in(file(".")) quill, refined, scalatest, + munit, specs2, ) @@ -397,6 +399,22 @@ lazy val scalatest = project ) .settings(noDottySettings) +lazy val munit = project + .in(file("modules/munit")) + .enablePlugins(AutomateHeaderPlugin) + .dependsOn(core) + .settings(doobieSettings) + .settings(publishSettings) + .settings( + name := s"doobie-munit", + description := "MUnit support for doobie.", + testFrameworks += new TestFramework("munit.Framework"), + libraryDependencies ++= Seq( + "org.scalameta" %% "munit" % munitVersion, + "com.h2database" % "h2" % h2Version % "test" + ) + ) + lazy val bench = project .in(file("modules/bench")) .enablePlugins(AutomateHeaderPlugin) @@ -407,7 +425,7 @@ lazy val bench = project lazy val docs = project .in(file("modules/docs")) - .dependsOn(core, postgres, specs2, hikari, h2, scalatest, quill) + .dependsOn(core, postgres, specs2, munit, hikari, h2, scalatest, quill) .enablePlugins(ParadoxPlugin) .enablePlugins(ParadoxSitePlugin) .enablePlugins(GhpagesPlugin) diff --git a/modules/docs/src/main/mdoc/docs/06-Checking.md b/modules/docs/src/main/mdoc/docs/06-Checking.md index 41f79b2f0..31beef1a9 100644 --- a/modules/docs/src/main/mdoc/docs/06-Checking.md +++ b/modules/docs/src/main/mdoc/docs/06-Checking.md @@ -99,7 +99,7 @@ def biggerThan2(minPop: Int) = biggerThan2(0).check.unsafeRunSync() ``` -**doobie** supports `check` for queries and updates in three ways: programmatically, via YOLO mode in the REPL, and via the `doobie-specs2` and `doobie-scalatest` packages, which allow checking to become part of your unit test suite. We will investigate this in the chapter on testing. +**doobie** supports `check` for queries and updates in four ways: programmatically, via YOLO mode in the REPL, and via the `doobie-specs2`, `doobie-scalatest` and `doobie-munit` packages, which allow checking to become part of your unit test suite. We will investigate this in the chapter on testing. ### Working Around Bad Metadata diff --git a/modules/docs/src/main/mdoc/docs/13-Unit-Testing.md b/modules/docs/src/main/mdoc/docs/13-Unit-Testing.md index afa5597ad..2c7daee76 100644 --- a/modules/docs/src/main/mdoc/docs/13-Unit-Testing.md +++ b/modules/docs/src/main/mdoc/docs/13-Unit-Testing.md @@ -1,6 +1,6 @@ ## Unit Testing -The YOLO-mode query checking feature demonstated in an earlier chapter is also available as a trait you can mix into your [Specs2](http://etorreborre.github.io/specs2/) or [ScalaTest](http://www.scalatest.org/) unit tests. +The YOLO-mode query checking feature demonstated in an earlier chapter is also available as a trait you can mix into your [Specs2](http://etorreborre.github.io/specs2/), [ScalaTest](http://www.scalatest.org/) or [MUnit](https://scalameta.org/munit) unit tests. ### Setting Up @@ -129,3 +129,25 @@ Details are shown for failing tests. // Run a test programmatically. Usually you would do this from sbt, bloop, etc. (new AnalysisTestScalaCheck).execute(color = false) ``` + +### The MUnit Package + +The `doobie-munit` add-on provides a mix-in trait that we can add to any `Assertions` implementation (like `FunSuite`) much like the ScalaTest package above. + +```scala mdoc:silent +import _root_.munit._ + +class AnalysisTestSuite extends FunSuite with doobie.munit.IOChecker { + + override val colors = doobie.util.Colors.None // just for docs + + val transactor = Transactor.fromDriverManager[IO]( + "org.postgresql.Driver", "jdbc:postgresql:world", "postgres", "" + ) + + test("trivial") { check(trivial) } + test("biggerThan") { check(biggerThan(0)) } + test("update") { check(update("", "")) } + +} +``` diff --git a/modules/munit/src/main/scala/doobie/munit/analysisspec.scala b/modules/munit/src/main/scala/doobie/munit/analysisspec.scala new file mode 100644 index 000000000..5a68e8b4c --- /dev/null +++ b/modules/munit/src/main/scala/doobie/munit/analysisspec.scala @@ -0,0 +1,67 @@ +// Copyright (c) 2013-2020 Rob Norris and Contributors +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package doobie.munit + +import cats.effect.{ Effect, IO } +import doobie.util.query.{Query, Query0} +import doobie.util.testing._ +import org.tpolecat.typename._ +import munit.Assertions +import munit.Location + +/** + * Module with a mix-in trait for specifications that enables checking of doobie `Query` and `Update` values. + * + * {{{ + * class ExampleSuite extends FunSuite with IOChecker { + * + * // The transactor to use for the tests. + * val transactor = Transactor.fromDriverManager[IO]( + * "org.postgresql.Driver", + * "jdbc:postgresql:world", + * "postgres", "" + * ) + * + * // Now just mention the queries. Arguments are not used. + * test("findByNameAndAge") { check(MyDaoModule.findByNameAndAge(null, 0)) } + * test("allWoozles") { check(MyDaoModule.allWoozles) } + * + * } + * }}} + */ +object analysisspec { + + trait Checker[M[_]] extends CheckerBase[M] { this: Assertions => + + def check[A: Analyzable](a: A)(implicit loc: Location) = checkImpl(Analyzable.unpack(a)) + + def checkOutput[A: TypeName](q: Query0[A])(implicit loc: Location) = + checkImpl(AnalysisArgs( + s"Query0[${typeName[A]}]", q.pos, q.sql, q.outputAnalysis + )) + + def checkOutput[A: TypeName, B: TypeName](q: Query[A, B])(implicit loc: Location) = + checkImpl(AnalysisArgs( + s"Query[${typeName[A]}, ${typeName[B]}]", q.pos, q.sql, q.outputAnalysis + )) + + private def checkImpl(args: AnalysisArgs)(implicit loc: Location) = { + val report = analyzeIO(args, transactor).unsafeRunSync() + if (!report.succeeded) { + fail( + formatReport(args, report, colors) + .padLeft(" ") + .toString + ) + } + } + } + + /** Implementation of Checker[IO] */ + trait IOChecker extends Checker[IO] { + self: Assertions => + val M: Effect[IO] = implicitly + } +} diff --git a/modules/munit/src/main/scala/doobie/munit/package.scala b/modules/munit/src/main/scala/doobie/munit/package.scala new file mode 100644 index 000000000..913be7c61 --- /dev/null +++ b/modules/munit/src/main/scala/doobie/munit/package.scala @@ -0,0 +1,12 @@ +// Copyright (c) 2013-2020 Rob Norris and Contributors +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package doobie + +package object munit { + + type Checker[M[_]] = analysisspec.Checker[M] + type IOChecker = analysisspec.IOChecker + +} diff --git a/modules/munit/src/test/scala/doobie/munit/CheckerTests.scala b/modules/munit/src/test/scala/doobie/munit/CheckerTests.scala new file mode 100644 index 000000000..7ba145aa0 --- /dev/null +++ b/modules/munit/src/test/scala/doobie/munit/CheckerTests.scala @@ -0,0 +1,36 @@ +// Copyright (c) 2013-2020 Rob Norris and Contributors +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package doobie.munit + +import cats.effect.{ ContextShift, IO } +import doobie.syntax.string._ +import doobie.util.transactor.Transactor +import munit._ +import scala.concurrent.ExecutionContext + +trait CheckerChecks[M[_]] extends FunSuite with Checker[M] { + + implicit def contextShift: ContextShift[M] + + lazy val transactor = Transactor.fromDriverManager[M]( + "org.h2.Driver", + "jdbc:h2:mem:queryspec;DB_CLOSE_DELAY=-1", + "sa", "" + ) + + test("trivial") { check(sql"select 1".query[Int]) } + + test("fail".fail) { check(sql"select 1".query[String]) } + + final case class Foo[F[_]](x: Int) + + test ("trivial case-class"){ check(sql"select 1".query[Foo[cats.Id]]) } + +} + +class IOCheckerCheck extends CheckerChecks[IO] with IOChecker { + def contextShift: ContextShift[IO] = + IO.contextShift(ExecutionContext.global) +}