I'm writing a highly asynchronous bit of code and I'm having problems testing it using Specs2 and Mockito. The problem is that the matchers are executed before the asynchronous code executes. I see that specs2 has await and eventually helpers - they look promising but I'm not sure how to use them.
Below is a stripped down example that illustrates the problem
SUT
package example.jt
import scala.concurrent._
import ExecutionContext.Implicits.global
import scala.util.{Try, Success, Failure}
trait Service {
def foo
def bar
def baz
}
class AnotherTest(svc: Service) {
def method(fail: Boolean) {
svc.baz
future {
Thread.sleep(3000)
pvt(fail) onComplete {
case Success(_) => svc.foo
case Failure(ex) => svc.bar
}
}
}
private def pvt(fail: Boolean):Future[Unit] = {
val p = Promise[Unit]
future {
Thread.sleep(2000)
if (fail) p failure (new RuntimeException("Failure"))
else p success ()
}
return p.future
}
}
Specs2 Test
package example.jt.test
import example.jt._
import org.specs2.specification._
import org.specs2.mutable._
import org.specs2.specification._
import org.specs2.mutable._
import org.specs2.mock._
class TestPromise extends Specification with Mockito {
"mocks in promises" should {
"Verify foo" in {
val svc = mock[Service]
val sut = new AnotherTest(svc)
sut.method(false)
there was one(svc).baz
there was one(svc).foo
there was no(svc).bar
}
"Verify bar" in {
val svc = mock[Service]
val sut = new AnotherTest(svc)
sut.method(true)
there was one(svc).baz
there was one(svc).bar
there was no(svc).foo
}
}
}
You simply need to wait on your future calls. Either by using Await directly:
import scala.concurrent._
import scala.concurrent.duration._
Await(sut.method(false), 10 seconds)
or by using .await on a matcher (look for await in the matchers guide):
sut.method(false) must not(throwAn[Exception]).await
Related
I have a simple test case which is using TestActorRef and eventually to verify a timeout call of a method. Here is details of 3 file sources:
TestRestUtility.scala
import play.api.http.HttpVerbs
import play.api.libs.ws.WSClient
import javax.inject.Inject
import scala.concurrent.Future
class TestRestUtility #Inject()(ws: WSClient) extends HttpVerbs {
import scala.concurrent.ExecutionContext.Implicits.global
def getHealthStatus(): Future[Int] = {
ws.url("https://www.google.com").get().map { response =>
response.status
}
}
}
TestHealthCheckActor.scala
import akka.actor.{Actor, ActorLogging, Props}
import play.api.http.Status
import javax.inject.Inject
import scala.concurrent.Future
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.util.{Failure, Success}
object TestHealthCheckActor {
def props(testRestUtility: TestRestUtility): Props = {
Props(new TestHealthCheckActor(testRestUtility: TestRestUtility))
}
}
class TestHealthCheckActor #Inject()(testRestUtility: TestRestUtility)
extends Actor with ActorLogging with Status {
import context.dispatcher
val checkPeriod: FiniteDuration = 1.seconds
var apiStatus: Int = _
override def preStart(): Unit = {
context.system.scheduler.scheduleWithFixedDelay(
0.milliseconds,
checkPeriod,
self,
RefreshHealthStatus
)
}
override def receive: Receive = {
case RefreshHealthStatus =>
val health: Future[Int] = testRestUtility.getHealthStatus()
health.onComplete({
case Success(result) =>
result match {
case OK => apiStatus = Status.OK
case _ => apiStatus = Status.REQUEST_TIMEOUT
}
case Failure(e) =>
println(e)
})
}
}
TestSpec.scala
import akka.actor.{ActorSystem, PoisonPill}
import akka.testkit.{ImplicitSender, TestActorRef, TestKit}
import org.mockito.Mockito
import org.mockito.Mockito.{times, verify, when}
import org.scalatest
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
import org.scalatest.concurrent.Eventually
import org.scalatest.matchers.should.Matchers
import org.scalatest.time.Span
import org.scalatest.wordspec.AnyWordSpecLike
import org.scalatestplus.mockito.MockitoSugar
import play.api.http.Status
import scala.concurrent.Future
class TestSpec extends TestKit(ActorSystem("HealthCheckActorSpec")) with AnyWordSpecLike with Matchers
with BeforeAndAfterEach with MockitoSugar with ImplicitSender with Eventually with BeforeAndAfterAll with Status with TestHarnessConstants {
import scala.concurrent.ExecutionContext.Implicits.global
var mockTestRestUtility: TestRestUtility = _
"HealthCheckActor" should {
"time out and call subscription api" in {
mockTestRestUtility = mock[TestRestUtility]
when(mockTestRestUtility.getHealthStatus()).thenReturn(Future(OK)).thenReturn(Future(OK)).thenReturn(Future(OK))
val healthCheckActorShortPeriod: TestActorRef[TestHealthCheckActor] = TestActorRef(TestHealthCheckActor.props(mockTestRestUtility))
eventually(timeout(Span(9, scalatest.time.Seconds)), interval(Span(1, scalatest.time.Seconds))) {
verify(mockTestRestUtility, Mockito.atLeast(3)).getHealthStatus()
}
healthCheckActorShortPeriod ! PoisonPill
}
}
}
As description about eventually in "scalatest-core_2.12-3.2.3-sources.jar", it tolerates unsuccessful attempts before giving up, so the test case is expected to have 3 calls the method getHealthStatus() to be successful as the returned value from mock. But I got a failed test case with below error message. I don't know why the method was called only one time:
testRestUtility.getHealthStatus();
Wanted *at least* 3 times:
-> at com.deere.isg.ingest.supporttool.testharness.TestSpec.$anonfun$new$8(TestSpec.scala:44)
But was 1 time:
-> at com.deere.isg.ingest.supporttool.testharness.TestHealthCheckActor$$anonfun$receive$1.applyOrElse(TestHealthCheckActor.scala:36)
org.mockito.exceptions.verification.TooFewActualInvocations:
testRestUtility.getHealthStatus();
Wanted *at least* 3 times:
-> at com.deere.isg.ingest.supporttool.testharness.TestSpec.$anonfun$new$8(TestSpec.scala:44)
But was 1 time:
-> at com.deere.isg.ingest.supporttool.testharness.TestHealthCheckActor$$anonfun$receive$1.applyOrElse(TestHealthCheckActor.scala:36)
Future(OK) will be considered successful by eventually because it doesn't throw an exception (technically neither would a failed Future, unless you unwrapped it in the eventually). Since the first call succeeded, eventually won't make any attempts after the first.
I have a simple example where I have a route that invokes an actor, however it seems to get stuck in an infinite loop and the http response never comes. I am using akka-actor version 2.6.15 and akka-http version 10.2.4. Here is the sample code, any help is appreciated.
package test
import akka.actor.{Actor, ActorRef, Props}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Route, _}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.pattern.ask
import akka.util.Timeout
import org.scalatest.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.concurrent.duration.DurationInt
case class TestMessage()
class TestActor extends Actor {
def receive: Receive = {
case _ => "response"
}
}
class AkkaHttpTest extends AnyWordSpec with Matchers with ScalatestRouteTest {
val testActor: ActorRef = system.actorOf(Props(new TestActor()), name = "TestActor")
implicit val timeout: Timeout = 15.seconds
implicit val defaultTimeout = RouteTestTimeout(15.seconds)
val route: Route = {
get {
pathSingleSlash {
complete((testActor ? TestMessage()).mapTo[String])
}
}
}
"Test" should {
"Return text" in {
Get() ~> route ~> check {
println(responseAs[String])
}
}
}
}
To reply to a message in Akka, you have to explicitly send the reply.
In your example:
def receive: Receive = {
case _ =>
sender ! "response"
}
I have an existing Scala play application which has a REST API that calls another external REST API. I want to mock the external Web service returning fake JSON data for internal tests. Based on example from: https://www.playframework.com/documentation/2.6.x/ScalaTestingWebServiceClients
I followed example exactly as in Documentation and I'm getting compiler errors due to deprecated class Action.
import play.core.server.Server
import play.api.routing.sird._
import play.api.mvc._
import play.api.libs.json._
import play.api.test._
import scala.concurrent.Await
import scala.concurrent.duration._
import org.specs2.mutable.Specification
import product.services.market.common.GitHubClient
class GitHubClientSpec extends Specification {
import scala.concurrent.ExecutionContext.Implicits.global
"GitHubClient" should {
"get all repositories" in {
Server.withRouter() {
case GET(p"/repositories") => Action {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
} { implicit port =>
WsTestClient.withClient { client =>
val result = Await.result(
new GitHubClient(client, "").repositories(), 10.seconds)
result must_== Seq("octocat/Hello-World")
}
}
}
}
}
object Action in package mvc is deprecated: Inject an ActionBuilder
(e.g. DefaultActionBuilder) or extend
BaseController/AbstractController/InjectedController
And this is the primary example from latest official docs which in fact contains a compile time error, given this example doesn't work how should be the proper way to easily mock an external API using Scala Play?
You may change your example to:
Server.withRouterFromComponents() { cs => {
case GET(p"/repositories") => cs.defaultActionBuilder {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
}
} { implicit port =>
WsTestClient.withClient { client =>
val result = Await.result(
new GitHubClient(client, "").repositories(), 10.seconds)
result should be(Seq("octocat/Hello-World"))
}
}
To be honest, I'm not 100% sure if this is the nicest way. However I have submitted a PR to the play framework so you might watch that space for comments from the makers.
If you're using standalone version of play-ws you can use this library https://github.com/f100ded/play-fake-ws-standalone like this
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import org.f100ded.play.fakews._
import org.scalatest._
import play.api.libs.ws.JsonBodyWritables._
import scala.concurrent.duration.Duration
import scala.concurrent._
import scala.language.reflectiveCalls
/**
* Tests MyApi HTTP client implementation
*/
class MyApiClientSpec extends AsyncFlatSpec with BeforeAndAfterAll with Matchers {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
behavior of "MyApiClient"
it should "put access token to Authorization header" in {
val accessToken = "fake_access_token"
val ws = StandaloneFakeWSClient {
case request # GET(url"http://host/v1/foo/$id") =>
// this is here just to demonstrate how you can use URL extractor
id shouldBe "1"
// verify access token
request.headers should contain ("Authorization" -> Seq(s"Bearer $accessToken"))
Ok(FakeAnswers.foo)
}
val api = new MyApiClient(ws, baseUrl = "http://host/", accessToken = accessToken)
api.getFoo(1).map(_ => succeed)
}
// ... more tests
override def afterAll(): Unit = {
Await.result(system.terminate(), Duration.Inf)
}
}
I'm developing web app using Scala, Play Framework. And I need to test controller with the custom action. Please, take a look on the code:
package controllers.helpers
import play.api.mvc.{ActionBuilder, Request, Result}
import scala.concurrent.Future
class CustomAction extends ActionBuilder[Request] {
override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]): Future[Result] = {
// do some stuff
block(request)
}
}
package controllers
import javax.inject.Singleton
import play.api.mvc.Controller
#Singleton
class SomeController #Inject() (customAction: CustomAction
) extends Controller {
def foo() = customAction(parse.json) { implicit request =>
// do some stuff and create result
}
}
And below you can find a code of the test class. I use Specs2 and I got org.mockito.exceptions.misusing.InvalidUseOfMatchersException on the line with customeActionMock.invokeBlock(any[Request[_]], any[(Request[_]) => Future[Result]]) returns mock[Future[Result]]
package controllers
import controllers.helpers.CustomAction
import org.specs2.mock.Mockito
import play.api.mvc.{Request, Result}
import play.api.test.{FakeHeaders, FakeRequest, PlaySpecification}
import scala.concurrent.Future
class SomeControllerSpec extends PlaySpecification with Mockito {
private val customeActionMock = mock[CustomAction]
customeActionMock.invokeBlock(any[Request[_]], any[(Request[_]) => Future[Result]]) returns mock[Future[Result]] //this doesn't work, exception is thrown there
"SomController" should {
"respond Ok on valid request" in {
val result = new UserController(customeActionMock).foo()(FakeRequest())
status(result) shouldEqual OK
}
}
}
I understand that I mock block parameter of the CustomAction incorrectly. Can someone help me to do it properly?
My project uses Play 2.5.x. I use scalatest. This is how I test controllers.
import org.scalatestplus.play.OneAppPerSuite
import org.scalatest._
import org.scalatest.time.{Millis, Seconds, Span}
import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.Future
class SomeControllerSpec extends FlatSpec with Matchers with ScalaFutures with OneAppPerSuite {
private val customeActionMock = new CustomAction // create an instance of a class
implicit val defaultPatience = PatienceConfig(timeout = Span(5,Seconds), interval = Span(500, Millis))
it should "respond Ok on valid request" in {
val resultF : Future[Result] = new UserController(customeActionMock).foo()(FakeRequest())
whenReady(resultF) { resultR =>
resultR.header.status shouldBe 200
}
}
}
Don't mock the CustomAction :
class SomeControllerSpec extends PlaySpecification with Mockito {
private val customeActionMock = new CustomAction
"SomController" should {
"respond Ok on valid request" in {
val result = new UserController(customeActionMock).foo()(FakeRequest())
status(result) shouldEqual OK
}
}
}
I want to create a scheduled task in Play 2.5. I found some resources related to this topic but none of them were for Play 2.5. I found out this resource related to what I am looking for and it looks good. Also on the same link there is a migration guide from 2.4 to 2.5.
The examples from older versions used GlobalSettings as base but this was deprecated in 2.5. The migration guide is important because it says that we should use dependency injection instead of extending this trait. I am not sure how to do that.
Can you give me some guidance?
You need to run sheduled task inside Akka Actor:
SchedulerActor.scala
package scheduler
import javax.inject.{Inject, Singleton}
import akka.actor.Actor
import org.joda.time.DateTime
import play.api.Logger
import scala.concurrent.ExecutionContext
#Singleton
class SchedulerActor #Inject()()(implicit ec: ExecutionContext) extends Actor {
override def receive: Receive = {
case _ =>
// your job here
}
}
Scheduler.scala
package scheduler
import javax.inject.{Inject, Named}
import akka.actor.{ActorRef, ActorSystem}
import play.api.{Configuration, Logger}
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
class Scheduler #Inject() (val system: ActorSystem, #Named("scheduler-actor") val schedulerActor: ActorRef, configuration: Configuration)(implicit ec: ExecutionContext) {
val frequency = configuration.getInt("frequency").get
var actor = system.scheduler.schedule(
0.microseconds, frequency.seconds, schedulerActor, "update")
}
JobModule.scala
package modules
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import scheduler.{Scheduler, SchedulerActor}
class JobModule extends AbstractModule with AkkaGuiceSupport {
def configure() = {
bindActor[SchedulerActor]("scheduler-actor")
bind(classOf[Scheduler]).asEagerSingleton()
}
}
application.conf
play.modules.enabled += "modules.JobModule"
If you don't want to use akka, you can use java:
ScheduledFuture
ScheduledExecutorService
DemoDaemon.scala:
import java.util.concurrent.{Executors, ScheduledFuture, TimeUnit}
import javax.inject._
import play.Configuration
import scala.util.Try
class DemoDaemon #Inject() (conf: Configuration) {
val isEnabled = conf.getBoolean("daemon.enabled")
val delay = conf.getLong("daemon.delay")
private var scheduledTaskOption : Option[ScheduledFuture[_]] = None
def task(): Unit = {
Try {
println("doSomething")
} recover {
case e: Throwable => println(e.getMessage)
}
}
def start(): Unit = {
if (isEnabled) {
val executor = Executors.newScheduledThreadPool(1)
scheduledTaskOption = Some(
executor.scheduleAtFixedRate(
new Runnable {
override def run() = task()
},
delay, delay, TimeUnit.SECONDS
)
)
} else {
println("not enabled")
}
}
def stop(): Unit = {
scheduledTaskOption match {
case Some(scheduledTask) =>
println("Canceling task")
val mayInterruptIfRunning = false
scheduledTask.cancel(mayInterruptIfRunning)
case None => println("Stopped but was never started")
}
}
}
DaemonService.scala
import javax.inject.Inject
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future
class DaemonService #Inject() (appLifecycle: ApplicationLifecycle, daemon: DemoDaemon) {
daemon.start()
appLifecycle.addStopHook{ () =>
Future.successful(daemon.stop())
}
}
JobModule.scala
import com.google.inject.AbstractModule
class JobModule extends AbstractModule {
def configure(): Unit = {
bind(classOf[DaemonService]).asEagerSingleton()
}
}
application.conf
daemon.enabled = true
daemon.delay = 10
play.modules.enabled += "com.demo.daemon.JobModule"