Consider following snippet; I have used WSClient here for some api calls via DI.
#Singleton
class SampleService #Inject()(ws: WSClient) {
def get(id:Long): JsValue ={
val trailingURL = s"/$id".toString
val wsRequest = ws.url(baseURL+trailingURL).addQueryStringParameters("access_token" -> authToken).get()
val wsResponse = Await.result(wsRequest, Duration.Inf)
Json.toJson(wsResponse.body)
}
}
And I need to write unit test for get method. I'm doing the following thing
val mockedWS = mock[WSClient]
val sparrowService = new SurveySparrowService(mockedWS)
"get method" should {
"return a valid result with valid id" in {
val result = sparrowService.get(66405)
println(result)
assert(result.toString == `the result i'll get`)
}
}
But the mocking fails and i get a null pointer exception in following line=>
val wsRequest = ws.url(baseURL+trailingURL).addQueryStringParameters("access_token" -> authToken).get()
Also when i'm using Json.toJson(wsResponse.body) i'm getting extra \ with each parameter in whole response.
can anyone help me solving these two problems. Thanks.
There is play-mockws, which exists solely because mocking a WSClient manually is really tedious.
// simulation of a GET request to http://dns/url
val ws = MockWS {
case (GET, "http://dns/url") => Action { Ok("http response") }
}
await(ws.url("http://dns/url").get()).body == "http response"
Further explanation:
Mocking a class / trait simply creates you an instance of that type out of thin air. You cannot do anything with that instance in general, calling any method on it will simply return null. If your code under test calls methods of this object, you must stub those methods with answers (i.e. simply return a prepared value).
For WSClient, this means you must stub the url method since this will be called by any code doing HTTP requests. But this method returns a WSRequest. So, you must mock this also... Any call on this new mock needs to be stubbed too, or else it will end in a NPE again. This really gets complicated very soon, and you probably don't understand your test code too well anymore. That's why play-mockws was created which makes it very easy to reason about calls to HTTP services in your Play application.
BTW, you may also combine play-mockws with the SIRD - String Interpolation Router DSL, which makes it even easier to extract values out of the routes or query parameters if you need to:
val ws = MockWS {
case GET(p"/$id") if id == "66405" =>
Action {
Results.Ok("...")
}
}
Related
I'm building an app for a friend and I use Firestore. What I want is to display a list of favorite places but for some reason, the list is always empty.
I cannot get the data from Firestore. This is my code:
fun getListOfPlaces() : List<String> {
val places = ArrayList<String>()
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
val name = document.data["name"].toString()
places.add(name)
}
}
}
return list;
}
If I try to print, let's say the size of the list in onCreate function, the size is always 0.
Log.d("TAG", getListOfPlaces().size().toString()); // Is 0 !!!
I can confirm Firebase is successfully installed.
What am I missing?
This is a classic issue with asynchronous web APIs. You cannot return something now, that hasn't been loaded yet. With other words, you cannot simply return the places list as a result of a method because it will always be empty due the asynchronous behavior of the onComplete function. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available.
But not only Cloud Firestore loads data asynchronously, almost all of modern other web APIs do, since it may take some time to get the data. But let's take an quick example, by placing a few log statements in the code, to see more clearly what I'm talking about.
fun getListOfPlaces() : List<String> {
Log.d("TAG", "Before attaching the listener!");
val places = ArrayList<String>()
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("TAG", "Inside onComplete function!");
for (document in task.result) {
val name = document.data["name"].toString()
places.add(name)
}
}
}
Log.d("TAG", "After attaching the listener!");
return list;
}
If we run this code will, the output in your logcat will be:
Before attaching the listener!
After attaching the listener!
Inside onComplete function!
This is probably not what you expected, but it explains precisely why your places list is empty when returning it.
The initial response for most developers is to try and "fix" this asynchronous behavior, which I personally recommend against it. Here is an excelent article written by Doug Stevenson that I'll highly recommend you to read.
A quick solve for this problem would be to use the places list only inside the onComplete function:
fun readData() {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
//Do what you need to do with your list
}
}
}
If you want to use the list outside, there is another approach. You need to create your own callback to wait for Firestore to return you the data. To achieve this, first you need to create an interface like this:
interface MyCallback {
fun onCallback(value: List<String>)
}
Then you need to create a function that is actually getting the data from the database. This method should look like this:
fun readData(myCallback : MyCallback) {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
myCallback.onCallback(list)
}
}
}
See, we don't have any return type anymore. In the end just simply call readData() function in your onCreate function and pass an instance of the MyCallback interface as an argument like this:
readData(object: MyCallback {
override fun onCallback(value: List<String>) {
Log.d("TAG", list.size.toString())
}
})
If you are using Kotlin, please check the other answer.
Nowadays, Kotlin provides a simpler way to achieve the same result as in the case of using a callback. This answer is going to explain how to use Kotlin Coroutines. In order to make it work, we need to add the following dependency in our build.gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.2.1"
This library that we use is called Module kotlinx-coroutines-play-services and is used for the exact same purpose. As we already know, there is no way we can return a list of objects as a result of a method because get() returns immediately, while the callback from the Task it returns will be called sometime later. That's the reason why we should wait until the data is available.
When calling "get()" on the Task object that is returned, we can attach a listener so we can get the result of our query. What we need to do now is to convert this into something that is working with Kotlin Coroutines. For that, we need to create a suspend function that looks like this:
private suspend fun getListOfPlaces(): List<DocumentSnapshot> {
val snapshot = placesRef.get().await()
return snapshot.documents
}
As you can see, we have now an extension function called await() that will interrupt the Coroutine until the data from the database is available and then return it. Now we can simply call it from another suspend method like in the following lines of code:
private suspend fun getDataFromFirestore() {
try {
val listOfPlaces = getListOfPlaces()
} catch (e: Exception) {
Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
}
}
The reason for having a empty list got perfectly answered by Alex Mamo above.
I just like to present the same thing without needing to add an extra interface.
In Kotlin you could just implement it like so:
fun readData(myCallback: (List<String>) -> Unit) {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
myCallback(list)
}
}
}
and then use it like so:
readData() {
Log.d("TAG", it.size.toString())
})
I apologize in advance if this is an XY problem.
tl;dr:
I'd like to have a compile-time map of type [Request.type, Response.type] so I can effectively say if I send message Request, a CLI should, at compile-time, know how to deserialize its expected Response, irrespective of the fact that it won't know what type of request is sent until runtime.
too long; still read:
I have a CLI which communicates with an HTTP server and depending on the type of message sent to the HTTP server, I'd like to validate the JSON response against a case case.
For instance, if I send the HTTP server an AddFoo message, I might want to validate that the JSON response can be deserialized into an AddedFoo, etc.
My current solution is quite hacky. Using play-json, I'm attempting to parse the JSON response using a mapping from config.mode (i.e., command issued to the CLI) to the expected responses' implicit Reads.
My code looks something like this:
val modeToResponseReads: Map[String, Reads[_]] = Map(
Modes.ADD_FOO -> AddedFoo.addedFooReads,
Modes.ADD_BOO -> AddedBoo.addedBooReads,
Modes.GET_WOO -> GetWooResponse.getWooReads,
)
parser.parse(args, MyConfig()) match {
case Some(config) => try {
val exec = new MyHttpExecutor(remoteUri, config)
val res = Await.result(exec.getResponse, 100.seconds)
// passing `Reads` to `as` because JsValue#as[T] cannot be
// applied at runtime -- only compile-time.
val _ = Json.parse(res.json.toString)
.as(modeToResponseReads(config.mode))
exec.actorSystem.terminate()
exec.wsClient.close()
} catch {
case t: Throwable => logger.error(t.getMessage)
}
case None => {
logger.error("Bad arguments.")
sys.exit(1)
}
}
While this works, it's an incredible kludge that becomes increasingly unmaintainable with an increasing number of messages. Further, I've found that this pattern will need to be replicated anywhere some type of validate or conversion will need to happen (e.g., Future[Any] being converted to Future[AddedFoo]).
Surely my approach isn't the right way... how is this traditionally done? If it is the right way (please no), are there optimizations that can be made?
I managed to accomplish this by encoding the contract directly into the child Request classes. Namely, the child Request classes would hold a ResponseType type with the base class enforcing the covariant type.
So I can do something like this:
abstract class Response
abstract class Request[+A <: Response]
case class Foo(id: String)
object Foo {
implicit val fooReads = Json.reads[Foo]
implicit val fooFormat = Json.format[Foo]
}
case class FooResponse(foo: Foo) extends Response {
def greet = println("woo hoo!")
}
object FooResponse {
implicit val fooRespReads = Json.reads[FooResponse]
implicit val fooRespFormat = Json.format[FooResponse]
}
case class FooRequest() extends Request[FooResponse] {
type ResponseType = FooResponse
}
object Main extends App {
val req: FooRequest = new FooRequest()
val foo = Foo("12345")
val resp = new FooResponse(foo)
val respJsonString = Json.toJson(resp).toString
println(Json.parse(respJsonString).as[req.ResponseType])
}
I have some simple messages with implicit Json.reads and Json.formats defined in their companion objects. All of these messages extend MyBaseMessage.
In other words, for any T <: MyBaseMessage, T is (de)serializable.
These messages represent simple CRUD operations to be performed on a cluster, so there's an Play server that will sit between a CLI sending JSON and the Cluster. Because the operations are simple, I should be able to make some very generic Actions on the Play side: when I receive JSON at an endpoint, deserialize the message according to the endpoint and forward that message to the cluster.
My ultimate goal is to do something like this:
// AddBooMessage extends MyBaseMessage
def addBoo = FooAction[AddBooMessage]
// AddMooMessage extends MyBaseMessage
def addMoo = FooAction[AddMooMessage]
// etc. ...
So when a request is sent to the route corresponding to the addBoo message, the request's JSON will be parsed into an AddBooMessage message and pushed to the cluster. Repeat ad nauseam.
I have the following written:
private def FooAction[T <: MyBaseMessage] = Action {
implicit request =>
parseAndForward[T](request)
}
private def parseAndForward[T <: MyBaseMessage](request: Request[AnyContent]) = {
val parsedRequest = Json.parse(request.body.toString).as[T]
Logger.info(s"Got '$parsedRequest' request. Forwarding it to the Cluster.")
sendToCluster(parsedRequest)
}
But I find the following error:
No Json deserializer found for type T. Try to implement an implicit Reads or Format for this type.
However, all of these messages are serializable and have both Reads and Format defined for them.
I tried passing (implicit fjs: Reads[T]) to parseAndForward in hopes to implicitly provide the Reads required (though it should already be implicitly provided), but it didn't help.
How can I solve this problem?
JsValue#as[A] needs an implicit Reads[A] in order to deserialize JSON to some type A. That is, the error message you're getting is caused because the compiler can't guarantee there is a Reads[T] for any type T <: MyBaseMessage. Assuming sendToCluster is parameterized the same way, this can easily by fixed by simply requiring an implicit Reads[T] in each method call. It sounds like you were close, and just needed to take things a step further by requiring the Reads[T] from FooAction, as well (since that call is where the type is determined).
private def FooAction[T <: MyBaseMessage : Reads] = Action { implicit request =>
parseAndForward[T](request)
}
private def parseAndForward[T <: MyBaseMessage : Reads](request: Request[AnyContent]) = {
val parsedRequest = Json.parse(request.body.toString).as[T]
Logger.info(s"Got '$parsedRequest' request. Forwarding it to the Cluster.")
sendToCluster(parsedRequest) // Assuming this returns a `Future[Result]`
}
If your intention if you use the above code by manually supplying the type parameter, this will work just fine.
There are some other improvements I think you can make here. First, if you always expect JSON, you should require the parse.json BodyParser. This will return a BadRequest if what is received isn't even JSON. Second, as will throw an exception if the received JSON cannot be deserialized into the expected type, you can use JsValue#validate to do this more safely and fold the result to handle the success and error cases explicitly. For example:
private def FooAction[T <: MyBaseMessage] = Action.async(parse.json) { implicit request =>
parseAndForward[T](request)
}
private def parseAndForward[T <: MyBaseMessage](request: Request[JsValue]) = {
request.body.validate[T].fold(
error => {
Logger.error(s"Error parsing request: $request")
Future.successful(BadRequest)
},
parsed => {
Logger.info(s"Got '$parsed' request. Forwarding it to the Cluster.")
sendToCluster(parsed)
}
)
}
I am a Scala newbie, extending someone else's code. The code uses the Play framework's JSON libraries. I am accessing objects of class Future[Option[A]] and Future[Option[List[B]]. The classes A and B each have their own JSON writes method, so each can return JSON as a response to a web request. I'm trying to combine these into a single JSON response that I can return as an HTTP response.
I thought creating a class which composes A and B into a single class would allow me to do this, something along these lines:
case class AAndB(a: Future[Option[A]], b: Future[Option[List[B]]])
object AAndB {
implicit val implicitAAndBWrites = Json.writes[AAndB]
}
But that fails all over the place. A and B are both structured like this:
sealed trait A extends SuperClass {
val a1: String = "identifier"
}
case class SubA(a2: ClassA2) extends A {
override val a1: String = "sub identifier"
}
object SubA {
val writes = Writes[SubA] { aa =>
Json.obj(
"a1" -> aa.a1
"a2" -> aa.a2
)
}
}
Since B is accessed as a List, the expected output would be along these lines:
{
"a":{
"a1":"val1",
"a2":"val2"
},
"b":[
{
"b1":"val 3",
"b2":"val 4"
},
{
"b1":"val 5",
"b2":"val 6"
},
{
"b1":"val 7",
"b2":"val 8"
}
]
}
Your help is appreciated.
As #cchantep mentioned in the comments on your question, having Futures as part of a case class declaration is highly unusual - case classes are great for encapsulating immutable domain objects (i.e that don't change over time) but as soon as you involve a Future[T] you potentially have multiple outcomes:
The Future hasn't completed yet
The Future failed
The Future completed successfully, and contains a T instance
You don't want to tangle up this temporal stuff with the act of converting to JSON. For this reason you should model your wrapper class with the Futures removed:
case class AAndB(a: Option[A], b: Option[List[B]])
object AAndB {
implicit val implicitAAndBWrites = Json.writes[AAndB]
}
and instead use Scala/Play's very concise handling of them in your Controller class to access the contents of each. In the below example, assume the existence of injected service classes as follows:
class AService {
def findA(id:Int):Future[Option[A]] = ...
}
class BListService {
def findBs(id:Int):Option[Future[List[B]]] = ...
}
Here's what our controller method might look like:
def showCombinedJson(id:Int) = Action.async {
val fMaybeA = aService.findA(id)
val fMaybeBs = bService.findBs(id)
for {
maybeA <- fMaybeA
maybeBs <- fMaybeBs
} yield {
Ok(Json.toJson(AAndB(maybeA, maybeBs)))
}
}
So here we launch both the A- and B-queries in parallel (we have to do this outside the for-comprehension to achieve this parallelism). The yield block of the for-comprehension will be executed only if/when both the Futures complete successfully - at which point it is safe to access the contents within. Then it's a simple matter of building an instance of the wrapper class, converting to JSON and returning an Ok result to Play.
Note that the result of the yield block will itself be inside a Future (in this case it's a Future[Result]) so we use Play's Action.async Action builder to handle this - letting Play deal with all of the actual waiting-for-things-to-happen.
I have JUnit test like that:
Test fun testCategoriesLoading() {
val subscriber = TestSubscriber<List<ACategory>>()
service.categories().subscribe(subscriber)
subscriber.awaitTerminalEvent()
subscriber.assertNoErrors()
}
service is Retrofit, that uses GsonConverter to deserialize json into
data class ACategory(val id: String, val title: String, val parentId: String?, val hasChildren: Boolean)
instances.
Test is passing, even if ACategory filled with id = null, title = null etc.
So, as far as i know, gson using reflection, and kotlin lazily resolves this nullability constraints on first access.
Is there any way to force this resolve?
Some good-looking solution without direct access to fields manually? I really don't want to write every assert by hand.
You could use the new Kotlin reflection. If you have an instance of ACategory, call
ACategory::class.memberProperties
.filter { !it.returnType.isMarkedNullable }
.forEach {
assertNotNull(it.get(aCategory))
}
to access all properties that are marked as not nullable and assert they're not null. Make sure, you have the reflection lib on the classpath.
Make sure you're using M14.
We ended up with hack for data classes(only use case for us, so its ok).
Calling gsonConstructedObject.copy() reveals all exceptions