I am writing unit tests for Play application using Scalamock and Scalatest.
My original code looks like:
// Here ws is an injected WSClient
val req = Json.toJson(someRequestObject)
val resp: Future[WSResponse] = ws.url(remoteURL).post(Json.toJson(req))
In a part I have to mock external calls to a web service, which I am trying to do using scalamock:
ws = stub[WSClient]
wsReq = stub[WSRequest]
wsResp = stub[WSResponse]
ws.url _ when(*) returns wsReq
wsReq.withRequestTimeout _ when(*) returns wsReq
(wsReq.post (_: java.io.File)).when(*) returns Future(wsResp)
I am successfully able to mock post requests using a file, but I cannot mock post requests using JSON.
I tried putting stub function references separately like:
val f: StubFunction1[java.io.File, Future[WSResponse]] = wsReq.post (_: java.io.File)
val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)
I get the compile error for second line: Unable to resolve overloaded method post
What am I missing here? Why cannot I mock one overloaded method but not the other one?
play.api.libs.ws.WSRequest has two post methods (https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.libs.ws.WSRequest), taking:
File
T (where T has an implicit bounds on Writeable)
The compiler is failing because you are trying to calling post with a single parameter, which only matches version 1. However, JsValue cannot be substituted with File.
You actually want to call the 2nd version, but this is a curried method that takes two sets of parameters (albeit the 2nd are implicit). Therefore you need to explicitly provide the mock value that you expect for the implicit, i.e.
val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)(implicitly[Writeable[JsValue]])
Therefore a working solution would be:
(wsReq.post(_)(_)).when(*) returns Future(wsResp)
Old answer:
WSRequest provides 4 overloads of post method (https://www.playframework.com/documentation/2.5.8/api/java/play/libs/ws/WSRequest.html), taking:
String
JsonNode
InputStream
File
You can mock with a File because it matches overload 4, but JsValue does not match (this is part of the Play JSON model, whereas JsonNode is part of the Jackson JSON model). If you convert to a String or JsonNode, then it will resolve the correct overload and compile.
My best guess is that your WSRequest is actually a play.libs.ws.WSRequest which is part of the Java API, instead you should use play.api.libs.ws.WSRequest which is the Scala API.
The method WSRequest.post exists and BodyWritable[JsValue] is implicitly provided by WSBodyWritables in the Scala API but not in the Java API.
Another cause could be that your JsValue is not a play.api.libs.json.JsValue but something else (e.g. spray.json.JsValue).
I'll quote an example where I have successfully achieved what you are trying to do, the main difference is that I used mock instead of stub.
The important part is:
val ws = mock[WSClient]
val responseBody = "{...}"
...
"availableBooks" should {
"retrieve available books" in {
val expectedBooks = "BTC_DASH ETH_DASH USDT_LTC BNB_LTC".split(" ").map(Book.fromString).map(_.get).toList
val request = mock[WSRequest]
val response = mock[WSResponse]
val json = Json.parse(responseBody)
when(ws.url(anyString)).thenReturn(request)
when(response.status).thenReturn(200)
when(response.json).thenReturn(json)
when(request.get()).thenReturn(Future.successful(response))
whenReady(service.availableBooks()) { books =>
books.size mustEqual expectedBooks.size
books.sortBy(_.string) mustEqual expectedBooks.sortBy(_.string)
}
}
}
An you could see the complete test at: BinanceServiceSpec
I guess it should work fine, if you mock a response that is JsValue.
when(wsReq.post(Json.parse("""{...json request...}"""))).thenReturn(Future(wsResp))
Here Json.parse returns JsValue. Yo should pass the json string that you expect in the request body.
Related
I'm trying to convert a few endpoints I have to use concurrency. I have the following method for the controller:
Original method
def getHistory(id:String) = Action {
val response = historyService.getPersonHistory(id)
Ok(write(response)) as "application/json"
}
New Method
def getHistory(id:String) = Action.async {
val response = scala.concurrent.Future {
historyService.getPersonHistory(id)
}
response.map(i => Ok(i))
}
So, when we try this with a simple example process (not calling another method, but just calculating an Int) it seems to work. In the new version above, I keep getting the error:
"No implicits found for parameter writable: Writeable[historyResponse]
Cannot write an instance of models.HistoryResponse to HTTP response. Try to define a Writeable[models.HistoryResponse]"
I'm new to Scala, and having difficulty finding information on making writeables. What do I need to be able to return the results as before?
Thanks
You need to define an implicit val tjs: Writes[HistoryResponse] or even better, implicit val format: Format[HistoryResponse] = Json.format[HistoryResponse] in the companion object for HistoryResponse, so that play can auto convert your data to json. by the way, not a good name for i in the map function, something like "history" would be better instead of "i".
I'm trying to test the following line of code using ScalaTest and ScalaMock.
val responseFuture = wsClient.url(url).withQueryString(params: _*).get()
wsClient type is THttpClient, which is a wrapper of play.api.libs.ws.WS.
Given that:
val mockHttpClient = mock[THttpClient]
is properly injected into my class under test, the test code is something like this:
val expectedUrl = "some url"
val mockRequestHolder = mock[WSRequestHolder]
inSequence {
(mockHttpClient.url _).expects(expectedUrl).returns(mockRequestHolder)
(mockRequestHolder.withQueryString _).expects(where {
(parameters: Seq[(String, String)]) => {
// assertions on parameters
// ...
true
}
}).returns(mockRequestHolder)
val stubResponse = stub[WSResponse]
val jsonBody = "{}"
(stubResponse.json _).when().returns(Json.parse(jsonBody))
(mockRequestHolder.get _).expects().returns(Future(stubResponse))
}
IntelliJ is highlighting mockRequestHolder.get as an error saying: cannot resolve symbol get. Nevertheless I'm able to run the test but the mock is clearly not working, and I'm getting: java.util.NoSuchElementException: JsError.get.
The mock is working when I try to mock any other method of WSRequestHolder, but not with method get.
Is this a ScalaMock bug or am I doing something wrong?
I don't know if you have solved already the issue, but I have tried to do something similar recently and I kind of got it working with the following code:
val wsClientMock = mock[WSClient]
val wsRequestMock = mock[WSRequest]
val wsResponseMock = mock[WSResponse]
(wsRequestMock.withAuth _).expects(username, password, WSAuthScheme.BASIC).returning(wsRequestMock)
(wsRequestMock.get _).expects().returning(Future[WSResponse](wsResponseMock))
(wsClientMock.url _).expects(bootstrapUrl).returning(wsRequestMock)
(wsResponseMock.status _).expects().returning(200)
"kind of" because I need to mock also the response, otherwise I get results like
ERROR[default-akka.actor.default-dispatcher-4] OneForOneStrategy - Unexpected call: json()
due to the fact that the code calling the WSClient is calling the method .json of WSResponse.
Sorry, I don't know Scala Mock but I suggest you to have a look at MockWS a library which comes with a mocked WS client: play-mockws
With MockWS you define a partial function which returns an Action for a Route. This enables you to precisely configure mocked answers and test your http client code.
I have a simple Spray scenario that doesn't work as I've expected.
I have a spray server that dispatches work to different modules, and then return their composed responses.
As I don't want to limit Module responses, I chose that the Module's method return ToResponseMarshallable, as I have Modules that need to return a plain String.
Module signature could be:
def moduleProcessing(): ToResponseMarshallable = randomString()
And my "complete" block look similar to this:
complete {
val response1 = moduleProcessing()
val response2 = moduleProcessing()
Seq(response1,response2)
}
In the previous example, I would expect to get:
[{"someRandomString"},{"anotherRandomString"}]
But I am getting:
[{},{}]
Of course it will propagate as expected, if I return a single response or if I change the signature of the moduleProcessing return type to any Marshallable Type.
Thanks in advance for your help!
I think it's strange that your moduleProcessing method returns directly a ToResponseMarshallable[T]. The spray way would be to have this method return a T and have an in scope Marshaller[T] for when you complete requests by providing a T.
Finally, I don't think Marshaller.of[Seq[T]] is a built in marshaller in spray.
This means you'll need to provide your own Seq marshaller. This could delegate the actual marshalling of each item in the seq to an implicit Marshaller[T] and concatenate the results. Many builtin spray marshallers require implicit Marshaller[T] values like this. Alternatively, if you want a simple Seq[T] to string marshaller, try something like the following:
implicit val CustomMarshaller = Marshaller.delegate[Seq[T], String](`text/html`)(_.mkString("{", ",", "}"))
I'm trying to add in a new element to a JsValue, but I'm not sure how to go about it.
val rJson = Json.parse(response)
val imgId = //stuff to get the id :Long
rJson.apply("imgId", imgId)
Json.stringify(rJson)
Should I be converting to a JSONObject or is there some method that can be applied directly to the JsValue to insert a new element to the JSON?
Edit:
response is coming from another server, but I do have control over it. So, if I need to add an empty "imgId" element to the JSON Object, that's fine.
You can do this as a JsObject, which extends JsValue and has a + method:
val rJson: JsValue = Json.parse(response)
val imgId = ...
val returnJson: JsObject = rJson.as[JsObject] + ("imgId" -> Json.toJson(imgId))
Json.stringify(returnJson)
I use the following helper in a project I'm working on:
/** Insert a new value at the given path */
def insert(path: JsPath, value: JsValue) =
__.json.update(path.json.put(value))
, which can be used as a JSON transformer as such:
val rJson = Json.parse(response)
val imgId = //stuff to get the id :Long
Json.stringify(rJson.transform(insert(__ \ 'imgId, imgId)))
You could definitely just use the body of that insert method, but I personally find the transformer API to be really counterintuitive.
The nice thing about this is that any number of transforms can be composed using andThen. We typically use this to convert API responses to the format of our models so that we can use Reads deserializers to instantiate model instances. We use this insert helper to mock parts of the API response that don't yet exist, so that we can build models we need ahead of the API.
Even though the API is convoluted, I highly, highly recommend investing some time in reading the Play framework docs on JSON handling, all 5 pages. They're not the world's greatest docs, but they actually are pretty thorough.
I am trying to consume an external Java Service API in Scala by writing a wrapper around it. Each API call has a Request class and Response class, as well as a factory method to setup a call in the underlaying java client i.e.
val caller = serviceClient.newDoSomething()
val request = DoSomethingRequest(...)
val response = caller.call(request)
where response is of type DoSomethingResponse.
This pattern is repeated for a bunch of different operations only varying in the names of the request/response classes and the factory method. I would like to DRY up the code and use the type inference, while noting a key observation that java method
caller.call is of type Request => Response.
Given that I've attempted the following:
class ServiceAPI {
val client = Service.getDefaultClient
def serviceCall[Req, Resp](auth: String, caller: Req => Resp)
(func: Req => Unit)(implicit m:Manifest[Req]):Resp = {
val request = m.erasure.newInstance.asInstanceOf[Req]
request.setAuth(auth)
func(request)
caller(request)
}
}
What I would like to end up with is a consumable scala API that can be invoked like so:
serviceCall(myAuth, client.newEventCall.call(_)) {
_.setFoo("foo") // On request
_.setBar(2) // On request
}
I was hoping that by providing the partial function client.newEventCall.call(_) Scala would be able to fully infer Req and Resp from it and then properly invoke the rest.
However, this fails with:
error: missing parameter type for expanded function ((x$4) => client.newEventCall.call(x$4))
Obviously I can provide the type manually via:
serviceCall(myAuth, client.newEventCall.call(_:EventRequest)) { ... }
but I would like to avoid that. Any ideas?
Also bonus points if this can be further simplified.