How to add a method to a mock on scala - json

I have a code like that:
class Object3 (
param1: String
param2: String
)
class Object1 (
param1: String
param2: Object2
) extends Message
class SampleClassSpec extends WordSpec with MockitoSugar {
"A sender" should {
"send a message" in {
val client = mock[Client]
var message = mock[Object1]
var detail = mock[Object2]
when(message.param2).thenReturn(detail)
when(message.param2.toJson).thenReturn(detail.toJson) // give me NPE
val sender = new Sender(client)
sender.prepareandsend(message) // second NPE
verify(client).send(message.param2.toJson.prettyPrint)
}
}
}
the problem is that I got NPE when I want to add the method to get formatted json, I don't want to send the whole Object1, just the Object3; I'm using spray.json._ for this.
if I remove the line that give me NPE, then I got a second NPE on "prepareandsend"
My question is how I can add this method toJson.prettyPrint to a mock?
Or maybe there is another way to do this. I'm a really beginner on this.

I don't know anything about Scala, but I have worked with Mockito before.
It seems to me that the problem is that you haven't set any behavior for detail.
Since it's a mock as well, it doesn't have any behavior, so it won't return anything when you call detail.toJson
You should add some behavior to detail with
when(detail.toJson).thenReturn(/* your JSON code */)

Try:
when(param2.toJson).thenReturn(detail.toJson)
instead.
Alternatively, you can look into using Mockitos DEEP_STUBS mechanism.

Related

Mocking WsClient in unit tests in play scala

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("...")
}
}

Gson - nice serialisation of Kotlin enums with a value

I have an enum with values
enum class Foo(val serverVal:Int) {
BAR(1),
BUG(2)
}
that I would like to use with a class:
data class C1(val fooVal:Foo)
I want to be able to serialise it with a code that looks as close as can be to:
Gson().toJson(C1(Foo.BAR))
That would yield
{"fooVal":1}
Instead of the default conversion, which is, of course {"fooVal":"BAR"}. If it was a string value I could have used #SerializedName, but I can't because it's an Int, not a String.
Is there a simple way to add something to Foo, in order to show Gson how to take the Int value from Foo entries rather than their name?
Examples I saw in Java include EnumAdapterFactory and TypeAdapter which are quite cumbersome and defeat the purpose of a pretty code.
I would love to get a solution that is maintained inside the data structure.
enum class SomeEnum(val i:Int) {
V1(1), V2(10), V3(5);
companion object {
#SerializeMeLikeThis
fun toJson() = i;
#DeserializeMeLikeThat
fun fromJson(v:Int) = values().find{it.i == v}?:whatever
}
}
An ugly way but still encapsulated-ish
data class C2(#SerializedName("foo") var serverFoo:Int) {
// Becomes a nightmare with many params.
constructor(f:Foo) : this(F.serverVal)
var foo:Foo
get() = Foo.values().find{serverFoo == it.serverVal}?:whatever
set(v) { serverFoo = v.serverVal }
}
So it can be called
Gson().toJson(C2(BAR))
and
// Result: Foo.BAR
Gson().fromJson("""{"foo":"1"}""", C2::class.java).foo
Well, you can live on it...but it... :-(
Any nice way?
Thanks

Use JSON.decode() in dart to instantiate class from json string

By browsing around I did successfully manage to create a class that can "opt in" dat:convert by exposing a Map toJson() method and can be json-ified with JSON.encode(myClass), more or less like the following:
//My dummy class
class MyClass{
String p1;
String p2;
Map toJson{
return {
'p1':this.p1,
'p2':this.p2
}
}
}
//so I can do
String jsonString = JSON.encode(myClass)
However I'd like to do this even the other way around, like:
String jsonString = '{"p1":"value","p2":"value"}'
MyClass instance = JSON.decode(jsonString)
But so far I've failed to find a way.
I know I can build a constructor for my class that initialises it from a map, something like:
String jsonString = '{"p1":"value","p2":"value"}'
MyClass instance = MyClass.fromMap(JSON.decode(jsonString))
However I was looking for a more "symmetric" way using just JSON.encode() and JSON.decode(), is it somehow doable? Am I missing something?
There is no standard way to encode the class in JSON. {"p1":"value","p2":"value"} doesn't contain any information about what class to instantiate. There is also no standard way to create a new class from as string (what library should be used when several contain a class with the same name, ...
As far as I know a reviver can be used for that purpose
reviver(var key, var value) {
// decode manually
}
final jsonDecoder = new JsonDecoder(reviver);
but the reviver would need to have some hardcoded logic how to recognize what JSON should result in what Dart class and how it should instantiate it and initialize the properties from the JSON.

Using ModelCompanion's fromJSON method in Play application throws a NoSuchMethodException

I'm new to Scala and I'm trying to do my first Play 2.0 application. I'm using:
Scala 2.9.3
Play 2.0.4
SalatWithPlay2 1.1
I have this in my conf/routes file:
PUT /newVenue controllers.Application.createNewVenue
I have this in my Application.scala file:
def createNewVenue = Action(parse.json) { request =>
val newVenue = Venue.fromJSON(request.body.toString)
Venue.insert(newVenue)
Ok("New Venue Created")
}
And this is the code for Venue.scala:
import play.api.Play.current
import com.novus.salat._
import com.novus.salat.global._
import com.novus.salat.annotations._
import com.novus.salat.dao._
import com.mongodb.casbah.Imports._
import se.radley.plugin.salat._
object Venue extends ModelCompanion[Venue, ObjectId] {
val collection = mongoCollection("venues")
val dao = new SalatDAO[Venue, ObjectId](collection = collection) {}
}
case class Venue(
#Key("_id") val venueId:ObjectId,
var playlist:Playlist,
var isPlaying:Boolean = false)
To test if this is working I send a PUT request to localhost:9000/newVenue with this JSON as a body:
{"venueId": 3,"playlist":{"playlistId":2,"currentSongPosition":5},"isPlaying":false}
And then I get this error:
[error] application -
! #6d7oco1hf - Internal server error, for request [PUT /newVenue] ->
play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[NoSuchMethodException: model.Venue$.apply$default$1()]]
at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:134) [play_2.9.1.jar:2.0.4]
at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:115) [play_2.9.1.jar:2.0.4]
at akka.actor.Actor$class.apply(Actor.scala:318) [akka-actor.jar:2.0.2]
at play.core.ActionInvoker.apply(Invoker.scala:113) [play_2.9.1.jar:2.0.4]
at akka.actor.ActorCell.invoke(ActorCell.scala:626) [akka-actor.jar:2.0.2]
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:197) [akka-actor.jar:2.0.2]
Caused by: java.lang.NoSuchMethodException: model.Venue$.apply$default$1()
at java.lang.Class.getMethod(Class.java:1605) ~[na:1.6.0_37]
at com.novus.salat.ConcreteGrater.defaultArg(Grater.scala:350) ~[salat-core_2.9.1-1.9.1.jar:1.9.1]
at com.novus.salat.ConcreteGrater.safeDefault(Grater.scala:360) ~[salat-core_2.9.1-1.9.1.jar:1.9.1]
at com.novus.salat.ConcreteGrater$$anonfun$6$$anonfun$apply$7.apply(Grater.scala:319) ~[salat-core_2.9.1-1.9.1.jar:1.9.1]
at com.novus.salat.ConcreteGrater$$anonfun$6$$anonfun$apply$7.apply(Grater.scala:319) ~[salat-core_2.9.1-1.9.1.jar:1.9.1]
at scala.Option.orElse(Option.scala:218) ~[scala-library.jar:0.11.3]
I think the line triggering the error is val newVenue = Venue.fromJSON(request.body.toString).
Does anyone have a clue of what's wrong?
I followed the tutorial in SalatWithPlay2 page and also followed some advices in similar problems), but I had no luck so far.
UPDATE:
I found a workaround, although it's not exactly a solution, but maybe it's helpful for somebody else and for understanding what is the actual solution.
If I remove the annotation #Key, the code of the case class Venue looks like this:
case class Venue(
val venueId:ObjectId,
var playlist:Playlist,
var isPlaying:Boolean = false)
And then I get this other error:
[RuntimeException: in: unexpected OID input class='net.liftweb.json.JsonAST$JInt', value='3']
And if instead of using ObjectId I use Long for example, the code looks like this:
case class Venue(
val venueId:Long,
var playlist:Playlist,
var isPlaying:Boolean = false)
And I get no error!
So the NoSuchMethodException apparently is related to the #Key annotation related to the _id. I also tried to rename venueId to _id and the same NoSuchMethodException error appeared. So the question is: why can't I use the #Key annotation to say which of my attributes maps to the object id of the document in the mongo database?
Besides that, regular Integers cannot be automatically converted to ObjectId instances for some other reason.
Thanks!
Just a shot in the dark, but can you try naming your object class to something else? Maybe to VenueCompanion? It seems it is possible the compiler is getting confused on which object to apply to. IE it is not applying to the case class Venue, that you want. I am curious if the exception is still the same afterwards.
So I found what was the problem.
The attributes of the Json sent in the body of the request must match the names of the attributes of the mongodb's document, not the attributes of the Scala object.
So if my class Venue is defined like this:
case class Venue(
#Key("_id") val venueId:ObjectId,
var playlist:Playlist,
var isPlaying:Boolean = false)
The Json I have to send in the PUT request is:
{"_id": 3,"playlist":{"playlistId":2,"currentSongPosition":5},"isPlaying":false}
And NOT:
{"venueId": 3,"playlist":{"playlistId":2,"currentSongPosition":5},"isPlaying":false}

Grails render encodeAsJSON GORM with ObjectId (mongodb)

Here is a Model
import org.bson.types.ObjectId
class Foo{
ObjectId id
String name
}
And here an action
def action = {
render(status:200, contentType:"application/json") {
['foo' : Foo.get(params.id)]
}
}
The action will return something like this
{"foo":{"class":"Foo","id":{"class":"org.bson.types.ObjectId","inc":340737392,"machine":-2019394572,"new":false,"time":1299107672000},"name":"fooName"]}
My question is, how can I send in the json the toString of the ObjectId, I don't want this
"id":{"class":"org.bson.types.ObjectId","inc":340737392,"machine":-2019394572,"new":false,"time":1299107672000}
I want something more like
"id":18893828183
I know I can select the parameters I want like:
def foo = Foo.get(params.id)
['foo' : 'Foo' :[id:foo.id.toString(), name:foo.name]]
But I don't want to declare always what I want to return as json, I want to return all the object, Foo.get(params.id).encodeAsJSON()...
Is there a way to override encodeAsJSON()
I already tried to add this
class Foo{
....
static transients : ['idStr']
def getIdStr(){
return this.id.toString()
}
....
}
But it's ignored in the encodeAsJSON()
I even tried this
class Foo{
....
def toJSON(){
def obj = this.encodeAsJSON()
def json = new JsonSlurper().parseText(obj);
json.idString = this.id.toString()
return json.toString()
}
...
}
this "works", but no....
because after this
render(status:200, contentType:"application/json") {
['foo' : Foo.get(params.id).toJSON()]
}
the render encode the json, so everything is "escaped"....
So what do you think is the solution, with a builder always defining what I want to return?
Hope, I made my question clear....
I'll start with the builder, hope you can give me another simpler / cleaner solution...
Thanks
edit
I just did a method that returns the object as a map so now I do something like this
render(status:200, contentType:"application/json") {
['foo' : getFooAsMap(Foo.get(params.id))]
}
Register this objectMarshaller at Bootstarp.groovy and it will work like a charm
import grails.converters.JSON
import org.bson.types.ObjectId
JSON.registerObjectMarshaller(ObjectId) {
return it.toStringMongod()
}
If you're going to be JSON-encoding your domain classes out to the web, I wonder if ObjectId might not be the best choice? The GORM/MongoDB integration allows you to use any type for the id. You could just declare it as a String type (which can be assigned as a toString of an ObjectId if you like to use that for its randomness) and then you don't need to worry about this mess. Any performance/scalability problems from this could be analysed/dealt with later, but I wouldn't expect there to be any unless it's a very large app.
Use GStrings in your map and you will get a numeric value for your ObjectId.
E.g.
render ["foo":"$foo.id"] as JSON
You can do this too:
def domainObj = YourDomainClass.get(params.id)
Map props = [:]
def domain = new DefaultGrailsDomainClass(YourDomainClass.class)
domain.properties.each{
props[it.name] = domainObj[it.name]
}
props["id"] = domainObj.id.toString()
render props as JSON
Or better yet, make it reusable. Put this closure someplace handy:
def mongoObjectResponse = {dobj ->
Map props = [:]
def domain = new DefaultGrailsDomainClass(YourDomainClass.class)
domain.properties.each{
props[it.name] = dobj[it.name]
}
props["id"] = dobj.id.toString()
// I like to leave room in my responses for messages and such
message = ""
obj = props
}
Then call like this from your controller:
return render(contentType: "text/json") {
mongoObjectResponse.delegate = delegate
mongoObjectResponse(domainObj)
}
Just define your domain class as
class Foo {
String id
String name
}
Instead of ObjectId