I have created a Workflow Processor Trait similar to the one mentioned below:
import org.scalatestplus.play._
import play.api.mvc._
import play.api.test._
import play.api.test.Helpers._
import org.scalatest.Matchers._
import org.scalamock.scalatest.MockFactory
import utils.OAuthUtils._
import utils.OAuthUtils
import utils.OAuthProcess
import play.api.Application
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import play.api.libs.json.JsResult
import play.api.libs.json.Json
import play.api.libs.json.JsSuccess
import play.api.libs.json.JsError
import play.api.libs.ws.WS
trait MyProcess[A] {
type B
type C
type D
def stage1(s: String): B
def stage2(b: B)(implicit e: ExecutionContext, app: Application, a: A): Future[C]
def stage3(c: C)(implicit e: ExecutionContext, app: Application, a: A): Future[JsResult[D]]
def process(s: String)(implicit e: ExecutionContext, app: Application, a: A): Future[JsResult[D]] = {
val b = stage1(s)
val f_d = for {
c <- stage2(b)
d <- stage3(c)
} yield d
f_d
}
}
Now I would like to Unit test this code. So I created a nicer little scalatest scalamock test suite:
class TestController extends PlaySpec with Results with MockFactory {
"MyController" must {
" testing for method process" in {
running(FakeApplication()) {
implicit val context = scala.concurrent.ExecutionContext.Implicits.global
implicit val s = "implicit String"
implicit val currentApp = play.api.Play.current
case class TestParms(s: String)
abstract class TestProcessor extends MyProcess[TestParms] {
type B = String
type C = String
type D = String
}
implicit val t = stub[TestProcessor]
t.stage1(_: String).when(token).returns(testToken)
(t.stage2(_: String)(_: ExecutionContext, _: Application, _: TestParms)).when(token, context, currentApp, tp).returns({
Future.successful(stage2String)
})
(t.stage3(_: String)(_: ExecutionContext, _: Application, _: TestParms)).when(token, context, currentApp, tp).returns({
Future.successful(Json.fromJson[String](Json.parse(stage3String)))
})
}
}
}
}
My expectation is to set this stub on a different class and test the class. Stubs (t.stage2 and t.stage3) compile fine but the following statement doesn't compile.
t.stage1(_: String).when(token).returns(testToken)
Compiler reports the following issue:
overloaded method value when with alternatives: (resultOfAfterWordApplication: org.scalatest.words.ResultOfAfterWordApplication)Unit <and> (f: => Unit)Unit cannot be applied to (String) TestController.scala /play-scala/test/controllers
Could someone help? I am finding it very difficult to write Unit tests for Scala classes as well as mocking them.
Scalatest versions from Build.sbt:
"org.scalatestplus" %% "play" % "1.2.0" % "test",
"org.scalamock" %% "scalamock-scalatest-support" % "3.2" % "test",
Try wrapping the invocation in parens
(t.stage1(_: String)).when(token).returns(testToken)
I believe scalac thinks you're trying to call when on an instance of String (since that would be what's returned by t.stage1(_)).
Related
I have a wrapper trait that extends BasicFormats from spray json https://github.com/spray/spray-json/blob/release/1.3.x/src/main/scala/spray/json/BasicFormats.scala
but I would like to override the behavior of implicit object BigDecimalJsonFormat extends JsonFormat[BigDecimal] to add rounding logic. Example being
import spray.json._
import scala.math.BigDecimal.RoundingMode
case class Foo(i: BigDecimal)
object Foo extends DefaultJsonProtocol {
implicit val roundedBigDecimalProtocol: JsonFormat[BigDecimal] =
new JsonFormat[BigDecimal] {
def write(f: BigDecimal) = JsNumber(f.setScale(2, RoundingMode.HALF_UP))
def read(json: JsValue): BigDecimal =
DefaultJsonProtocol.BigDecimalJsonFormat.read(json)
}
implicit val fooFormatter = jsonFormat1(this.apply)
}
scastie snippet:
https://scastie.scala-lang.org/9RNhajzGRDGMX5QsuAohVA
Great question, I wasn't even aware this may cause so many problems. The solution I am about to propose is probably not the cleanest, but it does the job... First of all, you don't have to extend DefaultJsonProtocol, you might also import it's members:
object Foo {
import DefaultJsonProtocol._
implicit val roundedBigDecimalProtocol: JsonFormat[BigDecimal] = ...
implicit val fooFormatter = jsonFormat1(this.apply)
}
This doesn't solve the problem but that way you can exclude some of the members so that they are not imported. Here's the syntax for that
import DefaultJsonProtocol.{BigDecimalJsonFormat => _, _}
It basically says: don't import BigDecimalJsonFormat but import the rest.
And the following code sums it up.
import spray.json._
import scala.math.BigDecimal.RoundingMode
case class Foo(i: BigDecimal)
object Foo {
import DefaultJsonProtocol.{BigDecimalJsonFormat => _, _}
implicit val roundedBigDecimalProtocol: JsonFormat[BigDecimal] =
new JsonFormat[BigDecimal] {
def write(f: BigDecimal) = JsNumber(f.setScale(2, RoundingMode.HALF_UP))
def read(json: JsValue): BigDecimal =
DefaultJsonProtocol.BigDecimalJsonFormat.read(json)
}
implicit val fooFormatter = jsonFormat1(this.apply)
}
I'm trying to mock a function using when but I keep getting this error
2 matchers expected, 1 recorded:
-> at com.concrete.test.LuigiHistoryServiceTest.testHistory(LuigiHistoryServiceTest.scala:23)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
And this is my test code
val service: LuigiHistoryService = spy(new LuigiHistoryService)
when(service.runQuery(Matchers.anyString(), Matchers.any[ResultSet => Seq[ExtendedTaskStatus]].apply))
.thenReturn(Seq.empty)
This is the signature of the method
def runQuery[T](query: String, fn: ResultSet => T): Seq[T] = {/* */}
I have tried the following and it seems fine:
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.mockito.MockitoSugar
import org.scalatest.{FunSuite, Matchers}
import org.mockito.Mockito._
import org.mockito.Matchers._
#RunWith(classOf[JUnitRunner])
class MyTest extends FunSuite with Matchers with MockitoSugar {
case class ResultSet()
case class ExtendedTaskStatus()
class LuigiHistoryService {
def runQuery[T](query: String, fn: ResultSet => T): Seq[T] = List()
}
test("simple test"){
val service: LuigiHistoryService = spy(new LuigiHistoryService)
when(service.runQuery(anyString(), any[ResultSet => Seq[ExtendedTaskStatus]].apply))
.thenReturn(Seq.empty)
def fun(r: ResultSet) = List(ExtendedTaskStatus)
service.runQuery("hi", fun)
verify(service)
}
}
Can you please update this code if it does not solve your issue?
Lets say I have this case class:
case class Foo(bar: String, baz: Boolean = false)
which is used in when decoding/encoding API requests/responses using akka-http-json
in an example similar to this:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives
import akka.stream.{ ActorMaterializer, Materializer }
import scala.io.StdIn
object ExampleApp {
private final case class Foo(bar: String, baz: Boolean = false)
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
Http().bindAndHandle(route, "127.0.0.1", 8000)
StdIn.readLine("Hit ENTER to exit")
system.terminate()
}
private def route(implicit mat: Materializer) = {
import Directives._
import FailFastCirceSupport._
import io.circe.generic.auto._
pathSingleSlash {
post {
entity(as[Foo]) { foo =>
complete {
foo
}
}
}
}
}
}
This works fine as long as the json message includes the baz field. However, I want to be able to send a json message {bar: "something"} and let the result use Foo's default value for baz. Is there any configuration in circe or akka-http-json that could make this work?
Also, would be nice to ignore the baz field when encoding to json again, but this is not that important.
Edit:
I know I can do something like this:
implicit val fooEncoder: Encoder[Foo] = new Encoder[Foo] {
final def apply(a: Foo): Json = Json.obj(
("id", Json.fromString(a.bar))
)
}
implicit val fooDecoder: Decoder[Foo] = new Decoder[Decoder] {
final def apply(c: HCursor): Decoder.Result[Decoder] =
for {
bar <- c.downField("bar").as[String]
} yield {
Foo(bar)
}
}
but was hoping for an easier-to-maintain solution, solving the general case of not requiring the default fields in the json message.
You can do this using the circe-generic-extras package. It's a separate dependency you have to put in your build. For sbt, that's:
libraryDependencies += "io.circe" %% "circe-generic-extras" % "0.8.0"
Then, in your route function, replace
import io.circe.generic.extras.Configuration
import io.circe.generic.auto._
with:
import io.circe.generic.extras.auto._
implicit val customConfig: Configuration = Configuration.default.withDefaults
The encoders this generates will always include the default fields.
For more info see the circe release notes at: https://github.com/circe/circe/releases/tag/v0.6.0-RC1
I am passing from SprayJsonSupport to argonaut based on this example.
After some code modification :
object ElevationJsonProtocol extends DefaultJsonProtocol {
implicit val locationCodec: CodecJson[Elevation] = casecodec2(Elevation, Elevation.unapply)("location", "elevation")
implicit val elevationCodec: CodecJson[Location] = casecodec2(Location, Location.unapply)("lat", "lng")
implicit def googleApiResultCodec: CodecJson[GoogleApiResult] = casecodec2(GoogleApiResult, GoogleApiResult.unapply)("status", "results")
}
I got this error
Error:(41, 42) not enough arguments for method unmarshal: (implicit evidence$1: spray.httpx.unmarshalling.FromResponseUnmarshaller[GoogleApiResult])spray.http.HttpResponse => GoogleApiResult.
Unspecified value parameter evidence$1.
val pipeline = sendReceive ~> unmarshal[GoogleApiResult]
^
I take a look at the unmarshall method:
def unmarshal[T](implicit evidence$1 : spray.httpx.unmarshalling.FromResponseUnmarshaller[T]) : scala.Function1[spray.http.HttpResponse, T]
How can I add the implicit parameter? and why I did not got such error whith the sprayJsonSupport ?
The hole code :
import spray.httpx.unmarshalling.FromResponseUnmarshaller
import scala.util.{Success, Failure}
import scala.concurrent.duration._
import akka.actor.ActorSystem
import akka.pattern.ask
import akka.event.Logging
import akka.io.IO
import spray.json.{JsonFormat, DefaultJsonProtocol}
import spray.can.Http
import spray.httpx.SprayJsonSupport
import spray.client.pipelining._
import spray.util._
import argonaut._, Argonaut._
case class Elevation(location: Location, elevation: Double)
case class Location(lat: Double, lng: Double)
case class GoogleApiResult(status: String, results: List[Elevation])
object ElevationJsonProtocol extends DefaultJsonProtocol {
implicit val locationCodec: CodecJson[Elevation] = casecodec2(Elevation, Elevation.unapply)("location", "elevation")
implicit val elevationCodec: CodecJson[Location] = casecodec2(Location, Location.unapply)("lat", "lng")
implicit def googleApiResultCodec: CodecJson[GoogleApiResult] = casecodec2(GoogleApiResult, GoogleApiResult.unapply)("status", "results")
}
object Main extends App {
// we need an ActorSystem to host our application in
implicit val system = ActorSystem("simple-spray-client")
import system.dispatcher // execution context for futures below
val log = Logging(system, getClass)
log.info("Requesting the elevation of Mt. Everest from Googles Elevation API...")
import ElevationJsonProtocol._
val pipeline = sendReceive ~> unmarshal[GoogleApiResult]
val responseFuture = pipeline (
Get("http://maps.googleapis.com/maps/api/elevation/json?locations=27.988056,86.925278&sensor=false")
)
responseFuture onComplete {
case Success(GoogleApiResult(_, Elevation(_, elevation) :: _)) =>
log.info("The elevation of Mt. Everest is: {} m", elevation)
shutdown()
case Success(somethingUnexpected) =>
log.warning("The Google API call was successful but returned something unexpected: '{}'.", somethingUnexpected)
shutdown()
case Failure(error) =>
log.error(error, "Couldn't get elevation")
shutdown()
}
def shutdown(): Unit = {
IO(Http).ask(Http.CloseAll)(1.second).await
system.shutdown()
}
}
I don't really use argonaut, I use play json with spray. But at a glance it seems like there needs to be an argonaut support trait/import pulled in for your implicit codecs to convert to spray's unmarshaller (similar thing is required for play json).
https://github.com/dwhjames/argonaut-spray
this library seems to be what you want. Your implicits and imports look fine, pulling in the library should solve your problem.
Old title: How do you show or assert that a List is TraversableOnce?
I have been trying to devise a trait for some classes. In the trait, I have tried either of:
//First try:
def addData[A <: Any](newTweets: => List[A]): Unit
//Second try
def addData[A <: List[Any]](newTweets: => A): Unit
//Third try
def addData[A <: List[Any]](newTweets: => A): Unit
I have a couple of different implementations, e.g.:
def addData[Future[JsValue]](newData: => List[Future[JsValue]]): Unit = {
dataList ++= newData
}
or
def addData[Tweet](newTweets: => List[Tweet]): Unit = {
tweetsList ++= newTweets
tweetsList = tweetsList.sortWith(
(a,b) => a.created_at.getTime < b.created_at.getTime
)
I get errors both in IntelliJ and by the compiler asserting something like the following:
JsonPublisher.scala:23: error: type mismatch;
found : List[Future[play.api.libs.json.JsValue]]
required: scala.collection.TraversableOnce[scala.concurrent.Future[play.api.libs.json.JsValue]]
dataList ++= newData
The part I'm most confused about is that I believe List should implement TransversableOnce. Also, this worked fine prior to making the classes extend from a custom trait. The only difference was that I didn't parameterize the method's type in the class, e.g., I had def addData(... instead of addData[Tweet](.... But if I leave I don't change the functional form to be parameterized when extending from a trait, I get a complaint that I haven't implemented addData.
I'd welcome both new approachs and hints as to why this error is occurring (since List should be TransversableOnce).
EDIT:
After forgetting lists and making everything TransversableOnce, I'm still getting a likely equivalent error but possibly more illuminating (just not to me ):
[ant:scalac] /home/brandon/CommVis/jvm-scala/tweet-serve/src/main/scala/edu/cornell/comm/twitter/JsonPublisher.scala:23: error: type mismatch;
[ant:scalac] found : TraversableOnce[Future[play.api.libs.json.JsValue]]
[ant:scalac] required: scala.collection.TraversableOnce[scala.concurrent.Future[play.api.libs.json.JsValue]]
[ant:scalac] dataList ++= newData
[ant:scalac] ^
[ant:scalac] /home/brandon/CommVis/jvm-scala/tweet-serve/src/main/scala/edu/cornell/comm/twitter/TweetPublisher.scala:38: error: type mismatch;
[ant:scalac] found : scala.collection.TraversableOnce[Tweet]
[ant:scalac] required: scala.collection.TraversableOnce[edu.cornell.comm.twitter.Tweet]
[ant:scalac] tweetsList ++= newTweets
I'll also include more complete code:
Trait
package edu.cornell.comm.api
import scala.collection.{TraversableOnce}
import scala.collection.mutable.{MutableList, Publisher}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import edu.cornell.comm.twitter.types.Tweet
import scala.collection.mutable.{Publisher}
trait MicroBlogPublisher[T] extends Publisher[Future[String]] {
def addData[T](newTweets: => TraversableOnce[T]): Unit
}
Implementation One
package edu.cornell.comm.twitter
import edu.cornell.comm.api.MicroBlogPublisher
import edu.cornell.comm.twitter.types.IOHelpers._
import edu.cornell.comm.twitter.types._
import play.api.libs.json.{JsValue, Json}
import scala.collection.mutable.{MutableList, Publisher}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
class JsonPublisher extends MicroBlogPublisher[Future[JsValue]] {
protected var dataList: MutableList[Future[JsValue]] = MutableList.empty
def addData[Future[JsValue]](newData: => TraversableOnce[Future[JsValue]]): Unit = {
dataList ++= newData
}
}
Implementation Two
package edu.cornell.comm.twitter
import edu.cornell.comm.api.MicroBlogPublisher
import play.api.libs.json.Json
import scala.collection.{TraversableOnce}
import scala.collection.mutable.{MutableList, Publisher}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import edu.cornell.comm.twitter.types._
import IOHelpers._
class TweetPublisher extends MicroBlogPublisher[Tweet] {
protected var tweetsList: MutableList[Tweet] = MutableList.empty
def addData[Tweet](newTweets: => TraversableOnce[Tweet]): Unit = {
tweetsList ++= newTweets
tweetsList = tweetsList.sortWith(
(a,b) => a.created_at.getTime < b.created_at.getTime
)
}
}
Quite funny case you've got.
Here is the correct code:
trait MicroBlogPublisher[T] extends Publisher[Future[String]] {
def addData(newTweets: => TraversableOnce[T]): Unit
}
class JsonPublisher extends MicroBlogPublisher[Future[JsValue]] {
protected var dataList: MutableList[Future[JsValue]] = MutableList.empty
override def addData(newData: => TraversableOnce[Future[JsValue]]): Unit = {
dataList ++= newData
}
}
The problem was that method addData declared own generic type T which was completely unrelated to the type parameter T of the trait. Now the funny part, in concrete implementation, for example JsonPublisher, the declaration def addData[Future[JsValue]](... actually declared two generic type parameters with confusing names Future and JsValue. But actually this declaration was the synonym of def addData[P[Q]](....
Now how you could have avoid this mistake.
For me Idea shows warning for the declaration of def addData[T] in MicroBlogPublisher saying: "Suspicious shadowing of the type parameter T". It should have pointed you that T is now what it appears to be. Next thing, when you are overriding methods, don't forget override keyword. When something is wrong with the signature of overriding method you'll be advised of it.