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.
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 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.
I'm trying to do a simple REST API call using akka-http, circe and akka-http-json (akka-http-circe in particular).
import io.circe.generic.auto._
object Blah extends FailFastCirceSupport {
//...
val fut: Future[Json] = Http().singleRequest(HttpRequest(uri = uri))
I'm expecting akka-http-circe to figure out how to unmarshal a HttpResponse to my wanted type (here, just Json). But it doesn't compile.
So I looked at some documentation and samples around, and tried this:
val fut: Future[Json] = Http().singleRequest(HttpRequest(uri = uri)).flatMap(Unmarshal(_).to)
Gives: type mismatch; found 'Future[HttpResponse]', required 'Future[Json]'. How should I expose an unmarshaller for the fetch?
Scala 2.12.4, akka-http 10.0.10, akka-http-circe 1.18.0
The working code seems to be:
val fut: Future[Json] = Http().singleRequest(HttpRequest(uri = uri)).flatMap(Unmarshal(_).to[Json])
When the target type is explicitly given, the compiler finds the necessary unmarshallers and this works. It works equally with a custom type in place of Json.
Further question: Is this the best way to do it?
I am trying a simple example where I am using a modification of the Dispatch example from the Scalatra site to make an async http request. The code is below. I get a compilation error that says value OK is not a member of String. I put together a standalone scala test with no Scalatra dependencies and it works as expected. I suspect OK is being pulled in from some Scalatra dependency.
I am new to Scala and I am putting together a test web app using Scalatra. Any help will be appreciated.
import dispatch._
trait AppAPIStack extends AppStack {
before() {
contentType = "application/json"
}
object MyAsyncClient {
def sendReq(phrase: param): Future[String] = {
val searchURL = url("https://www.google.com/#q="+phrase)
val result = dispatch.Http(searchURL OK as.String)
for(r <- result) yield r
}
}
}
I fixed the issue by changing the line
url("https://www.google.com/#q="+phrase)
to
dispatch.url("https://www.google.com/#q="+phrase)
Dispatch and Scalatra base servlet I think have an implementation of url which was clashing. The url version from Scalatra does not return OK.
First of all inspect type of result. If you use IDE just hover your mouse on it and see what it is actually is. It must be different than Future[String], it is Future[T], find out what is T and how to convert it to String.
I am trying out ScalaMock in my scala application
What i have is an RSSReader which reads data from XML using XML.load(<urlString>), like in the code below
class ScalaRssFinancialDataReader
....
def fetchRSS(url:String) = XML.load(url)
....
}
I am mocking it like this
"fetching global economics mocking XML trait" should "return data" in {
val xmlFragment = <item><title>foo</title><author>a</author></item>
val xmlMock = mock[scala.xml.XML]
val tradingEconomicsUrl = "http://www.tradingeconomics.com/russia/rss"
(xmlMock.load_).expects(tradingEconomicsUrl).returns(xmlFragment)
val rssReader = new com.worldcorpservices.rss.reader.ScalaRssFinancialDataReader()
val res = rssReader.fetchRssData("http://www.tradingeconomics.com/russia/rss", "RUSSIA")
assert(res.size() == 1)
}
the problem is that i keep on getting exception that 'XML is not part of p ackage scala.xml
What am i doing wrong here? is it possible to mock XML.load method?
kind regards
marco
scala.xml.XML is an object, Mockito cannot mock objects, you may want to check ScalaMock (expecially here) for that. What I usually do is create a test file and use that as test case, you don't really need to mock the method.
Check also this question and this question.