From 38a61a950041ccdfbbfc5aa89fe72cd67887404e Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Tue, 9 Apr 2024 22:05:39 +0200 Subject: [PATCH 1/2] Move from mockito to scalamock (#62) --- build.sbt | 2 +- .../http/metrics/core/HttpMetricsSpec.scala | 105 ++++++++++-------- .../http/metrics/core/MeterStageSpec.scala | 58 +++++----- project/Dependencies.scala | 4 +- 4 files changed, 97 insertions(+), 72 deletions(-) diff --git a/build.sbt b/build.sbt index ac01a0e..f463016 100644 --- a/build.sbt +++ b/build.sbt @@ -64,10 +64,10 @@ lazy val `pekko-http-metrics-core` = (project in file("core")) Dependencies.PekkoHttp, Dependencies.Provided.PekkoStream, Dependencies.Test.Logback, - Dependencies.Test.Mockito, Dependencies.Test.PekkoHttpTestkit, Dependencies.Test.PekkoSlf4j, Dependencies.Test.PekkoStreamTestkit, + Dependencies.Test.ScalaMock, Dependencies.Test.ScalaTest ) ) diff --git a/core/src/test/scala/fr/davit/pekko/http/metrics/core/HttpMetricsSpec.scala b/core/src/test/scala/fr/davit/pekko/http/metrics/core/HttpMetricsSpec.scala index 491c6ec..c281da4 100644 --- a/core/src/test/scala/fr/davit/pekko/http/metrics/core/HttpMetricsSpec.scala +++ b/core/src/test/scala/fr/davit/pekko/http/metrics/core/HttpMetricsSpec.scala @@ -24,9 +24,8 @@ import org.apache.pekko.http.scaladsl.server.{RequestContext, RouteResult} import org.apache.pekko.stream.scaladsl.Keep import org.apache.pekko.stream.testkit.scaladsl.{TestSink, TestSource} import org.apache.pekko.testkit.TestKit -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.{mock, when} +import org.scalamock.matchers.ArgCapture.CaptureOne +import org.scalamock.scalatest.MockFactory import org.scalatest.BeforeAndAfterAll import org.scalatest.concurrent.ScalaFutures import org.scalatest.flatspec.AnyFlatSpecLike @@ -38,23 +37,22 @@ class HttpMetricsSpec extends TestKit(ActorSystem("HttpMetricsSpec")) with AnyFlatSpecLike with Matchers + with MockFactory with ScalaFutures with BeforeAndAfterAll { implicit val ec: ExecutionContext = system.dispatcher - private def anyRequestContext() = any(classOf[RequestContext]) - private def anyRequest() = any(classOf[HttpRequest]) - abstract class Fixture[T] { - val metricsHandler: HttpMetricsHandler = - mock(classOf[HttpMetricsHandler]) - val server: Function[RequestContext, Future[RouteResult]] = - mock(classOf[Function[RequestContext, Future[RouteResult]]]) + val handler = mock[HttpMetricsHandler] + val server = mockFunction[RequestContext, Future[RouteResult]] + + (handler.onConnection _).expects().returns((): Unit) + (handler.onDisconnection _).expects().returns((): Unit) val (source, sink) = TestSource .probe[HttpRequest] - .via(HttpMetrics.meterFlow(metricsHandler).join(HttpMetrics.metricsRouteToFlow(server))) + .via(HttpMetrics.meterFlow(handler).join(HttpMetrics.metricsRouteToFlow(server))) .toMat(TestSink.probe[HttpResponse])(Keep.both) .run() @@ -97,80 +95,99 @@ class HttpMetricsSpec } it should "call the metrics handler on handled requests" in new Fixture { - val request: ArgumentCaptor[HttpRequest] = ArgumentCaptor.forClass(classOf[HttpRequest]) - val response: ArgumentCaptor[HttpResponse] = ArgumentCaptor.forClass(classOf[HttpResponse]) - when(metricsHandler.onRequest(request.capture())) - .thenAnswer(_.getArgument(0)) + val request = CaptureOne[HttpRequest]() + val response = CaptureOne[HttpResponse]() + + (handler.onRequest _) + .expects(capture(request)) + .onCall { (req: HttpRequest) => req } - when(server.apply(anyRequestContext())) - .thenAnswer(invocation => complete(StatusCodes.OK)(invocation.getArgument[RequestContext](0))) - when(metricsHandler.onResponse(anyRequest(), response.capture())) - .thenAnswer(_.getArgument(1)) + server + .expects(*) + .onCall(complete(StatusCodes.OK)) + (handler.onResponse _) + .expects(*, capture(response)) + .onCall { (_: HttpRequest, resp: HttpResponse) => resp } + + val expectedRequest = HttpRequest() sink.request(1) - source.sendNext(HttpRequest()) + source.sendNext(expectedRequest) sink.expectNext() source.sendComplete() sink.expectComplete() - val expected = Marshal(StatusCodes.OK) + val expectedResponse = Marshal(StatusCodes.OK) .to[HttpResponse] .futureValue - response.getValue shouldBe expected + request.value shouldBe expectedRequest + response.value shouldBe expectedResponse } it should "call the metrics handler on rejected requests" in new Fixture { - val request: ArgumentCaptor[HttpRequest] = ArgumentCaptor.forClass(classOf[HttpRequest]) - val response: ArgumentCaptor[HttpResponse] = ArgumentCaptor.forClass(classOf[HttpResponse]) - when(metricsHandler.onRequest(request.capture())) - .thenAnswer(_.getArgument(0)) + val request = CaptureOne[HttpRequest]() + val response = CaptureOne[HttpResponse]() + (handler.onRequest _) + .expects(capture(request)) + .onCall { (req: HttpRequest) => req } - when(server.apply(anyRequestContext())) - .thenAnswer(invocation => reject(invocation.getArgument[RequestContext](0))) + server + .expects(*) + .onCall(reject) - when(metricsHandler.onResponse(anyRequest(), response.capture())) - .thenAnswer(_.getArgument(1)) + (handler.onResponse _) + .expects(*, capture(response)) + .onCall { (_: HttpRequest, resp: HttpResponse) => resp } + val expectedRequest = HttpRequest() sink.request(1) - source.sendNext(HttpRequest()) + source.sendNext(expectedRequest) sink.expectNext() source.sendComplete() sink.expectComplete() - val expected = Marshal(StatusCodes.NotFound -> "The requested resource could not be found.") + val expectedResponse = Marshal(StatusCodes.NotFound -> "The requested resource could not be found.") .to[HttpResponse] .futureValue .addAttribute(PathLabeler.key, "unhandled") - response.getValue shouldBe expected + + request.value shouldBe expectedRequest + response.value shouldBe expectedResponse } it should "call the metrics handler on error requests" in new Fixture { - val request: ArgumentCaptor[HttpRequest] = ArgumentCaptor.forClass(classOf[HttpRequest]) - val response: ArgumentCaptor[HttpResponse] = ArgumentCaptor.forClass(classOf[HttpResponse]) - when(metricsHandler.onRequest(request.capture())) - .thenAnswer(_.getArgument(0)) + val request = CaptureOne[HttpRequest]() + val response = CaptureOne[HttpResponse]() + (handler.onRequest _) + .expects(capture(request)) + .onCall { (req: HttpRequest) => req } - when(server.apply(anyRequestContext())) - .thenAnswer(invocation => failWith(new Exception("BOOM!"))(invocation.getArgument[RequestContext](0))) + server + .expects(*) + .onCall(failWith(new Exception("BOOM!"))) - when(metricsHandler.onResponse(anyRequest(), response.capture())) - .thenAnswer(_.getArgument(1)) + (handler.onResponse _) + .expects(*, capture(response)) + .onCall { (_: HttpRequest, resp: HttpResponse) => resp } + val expectedRequest = HttpRequest() sink.request(1) - source.sendNext(HttpRequest()) + source.sendNext(expectedRequest) sink.expectNext() source.sendComplete() sink.expectComplete() - val expected = Marshal(StatusCodes.InternalServerError) + val expectedResponse = Marshal(StatusCodes.InternalServerError) .to[HttpResponse] .futureValue .addAttribute(PathLabeler.key, "unhandled") - response.getValue shouldBe expected + + request.value shouldBe expectedRequest + response.value shouldBe expectedResponse } } diff --git a/core/src/test/scala/fr/davit/pekko/http/metrics/core/MeterStageSpec.scala b/core/src/test/scala/fr/davit/pekko/http/metrics/core/MeterStageSpec.scala index 4e6ab3e..d60c44f 100644 --- a/core/src/test/scala/fr/davit/pekko/http/metrics/core/MeterStageSpec.scala +++ b/core/src/test/scala/fr/davit/pekko/http/metrics/core/MeterStageSpec.scala @@ -22,7 +22,7 @@ import org.apache.pekko.stream.ClosedShape import org.apache.pekko.stream.scaladsl.{GraphDSL, RunnableGraph} import org.apache.pekko.stream.testkit.scaladsl.{TestSink, TestSource} import org.apache.pekko.testkit.TestKit -import org.mockito.Mockito.{mock, verify, when} +import org.scalamock.scalatest.MockFactory import org.scalatest.concurrent.ScalaFutures import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers @@ -31,13 +31,24 @@ class MeterStageSpec extends TestKit(ActorSystem("MeterStageSpec")) with AnyFlatSpecLike with Matchers + with MockFactory with ScalaFutures { private val request = HttpRequest() private val response = HttpResponse() + private val error = new Exception("BOOM!") trait Fixture { - val handler: HttpMetricsHandler = mock(classOf[HttpMetricsHandler]) + val handler = stub[HttpMetricsHandler] + + (handler.onConnection _).when().returns((): Unit) + (handler.onDisconnection _).when().returns((): Unit) + (handler.onRequest _).when(request).returns(request) + (handler.onResponse _).when(request, response).returns(response) + (handler.onFailure _).when(request, error).returns(error) + (handler.onFailure _) + .when(request, MeterStage.PrematureCloseException) + .returns(MeterStage.PrematureCloseException) val (requestIn, requestOut, responseIn, responseOut) = RunnableGraph .fromGraph( @@ -73,22 +84,21 @@ class MeterStageSpec responseIn.sendComplete() responseOut.expectComplete() - verify(handler).onConnection() - verify(handler).onDisconnection() + (handler.onConnection _).verify() + (handler.onDisconnection _).verify() } it should "call onRequest wen request is offered" in new Fixture { - when(handler.onRequest(request)).thenReturn(request) requestIn.sendNext(request) requestOut.expectNext() shouldBe request - when(handler.onResponse(request, response)).thenReturn(response) responseIn.sendNext(response) responseOut.expectNext() shouldBe response + + (handler.onRequest _).verify(request) } it should "flush the stream before stopping" in new Fixture { - when(handler.onRequest(request)).thenReturn(request) requestIn.sendNext(request) requestOut.expectNext() shouldBe request @@ -97,71 +107,69 @@ class MeterStageSpec requestOut.expectComplete() // response should still be accepted - when(handler.onResponse(request, response)).thenReturn(response) responseIn.sendNext(response) responseOut.expectNext() shouldBe response + + (handler.onRequest _).verify(request) + (handler.onResponse _).verify(request, response) } it should "propagate error from request in" in new Fixture { - when(handler.onRequest(request)).thenReturn(request) - requestIn.sendNext(request) requestOut.expectNext() shouldBe request - val error = new Exception("BOOM!") requestIn.sendError(error) requestOut.expectError(error) + + (handler.onRequest _).verify(request) } it should "propagate error from request out" in new Fixture { - when(handler.onRequest(request)).thenReturn(request) - requestIn.sendNext(request) requestOut.expectNext() shouldBe request - val error = new Exception("BOOM!") requestOut.cancel(error) requestIn.expectCancellation() + + (handler.onRequest _).verify(request) } it should "terminate and fail pending" in new Fixture { - when(handler.onRequest(request)).thenReturn(request) - requestIn.sendNext(request) requestIn.sendComplete() requestOut.expectNext() shouldBe request requestOut.expectComplete() - when(handler.onFailure(request, MeterStage.PrematureCloseException)).thenReturn(MeterStage.PrematureCloseException) responseIn.sendComplete() responseOut.expectComplete() + + (handler.onRequest _).verify(request) + (handler.onFailure _).verify(request, MeterStage.PrematureCloseException) } it should "propagate error from response in and fail pending" in new Fixture { - when(handler.onRequest(request)).thenReturn(request) - requestIn.sendNext(request) requestIn.sendComplete() requestOut.expectNext() shouldBe request requestOut.expectComplete() - val error = new Exception("BOOM!") - when(handler.onFailure(request, error)).thenReturn(error) responseIn.sendError(error) responseOut.expectError(error) + + (handler.onRequest _).verify(request) + (handler.onFailure _).verify(request, error) } it should "propagate error from response out and fail pending" in new Fixture { - when(handler.onRequest(request)).thenReturn(request) - requestIn.sendNext(request) requestIn.sendComplete() requestOut.expectNext() shouldBe request requestOut.expectComplete() - val error = new Exception("BOOM!") - when(handler.onFailure(request, error)).thenReturn(error) responseOut.cancel(error) responseIn.expectCancellation() + + (handler.onRequest _).verify(request) + (handler.onFailure _).verify(request, error) } } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bbad1b7..d426346 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,12 +8,12 @@ object Dependencies { val DropwizardV5 = "5.0.0" val Enumeratum = "1.7.3" val Logback = "1.5.3" - val Mockito = "5.9.0" val Pekko = "1.0.2" val PekkoHttp = "1.0.1" val Prometheus = "0.16.0" val ScalaCollectionCompat = "2.11.0" val ScalaLogging = "3.9.5" + val ScalaMock = "6.0.0-M2" val ScalaTest = "3.2.18" } @@ -35,7 +35,6 @@ object Dependencies { val DropwizardJvm = "io.dropwizard.metrics" % "metrics-jvm" % Versions.Dropwizard % "test" val DropwizardV5Jvm = "io.dropwizard.metrics5" % "metrics-jvm" % Versions.DropwizardV5 % "test" val Logback = "ch.qos.logback" % "logback-classic" % Versions.Logback % "test" - val Mockito = "org.mockito" % "mockito-core" % Versions.Mockito % "test" val PekkoHttpJson = "org.apache.pekko" %% "pekko-http-spray-json" % Versions.PekkoHttp % "test" val PekkoHttpTestkit = "org.apache.pekko" %% "pekko-http-testkit" % Versions.PekkoHttp % "test" val PekkoSlf4j = "org.apache.pekko" %% "pekko-slf4j" % Versions.Pekko % "test" @@ -44,6 +43,7 @@ object Dependencies { val PrometheusHotspot = "io.prometheus" % "simpleclient_hotspot" % Versions.Prometheus % "test" val ScalaCollectionCompat = "org.scala-lang.modules" %% "scala-collection-compat" % Versions.ScalaCollectionCompat % "test" + val ScalaMock = "org.scalamock" %% "scalamock" % Versions.ScalaMock % "test" val ScalaTest = "org.scalatest" %% "scalatest" % Versions.ScalaTest % "test" } } From 54a9222f8f50aa2e20aff4dab68a7063a822687f Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 9 Apr 2024 22:07:55 +0200 Subject: [PATCH 2/2] Update scalafmt-core to 3.8.1 (#65) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 09cb49d..4c7be0c 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.8.0 +version = 3.8.1 project.git = true # only format files tracked by git maxColumn = 120 runner.dialect = scala212source3