I try to write a unit test for a small function in a controller using Play/ScalaTest+ Play. The function I want to test looks like this:
def functionToTest(id: String) = Action.async {
implicit request =>
lang
deeperFunction{ implicit context =>
...
}
}
The deeperFunction
def deeperFunction(block: Context => Future[Result])(implicit request: RequestHeader): Future[Result] = {
// returns Future.successful(Found("DummyUrltoRedirect"))
}
}
The deeperFunction is inherited from a trait and I don't want use the real one here because it's a unit test and so I want to use a matcher instead
val deeperMock = mock[Rainmaker]
val contextMock = mock[Context]
val controller = new Controller()(.....) // list of implicit arguments
"Controller" must {
"return something" in {
val request = FakeRequest("GET", "/something")
when(deeperMock.deeperFunction(anyObject)(anyObject)) thenReturn Future.successful(Found("DummyUrlToRedirect"))
val id = "id"
val result = controller.functionToTest(id).apply(request)
status(result) mustBe Ok
}
}
But when I run this, the line "val result = controller.functionToTest(id).apply(request)" still seems to call the real deeperFunction, not the fake one and therefore throws a null matcher at some point.
I also tried to use
when(controller.deeperFunction(anyObject)(anyObject)) thenReturn Future.successful(Found("DummyUrlToRedirect"))
instead, because the deeperFunction is inherited, but with the same result.
I tried to stick to theses instructions
ScalaTest+Play
dzone
but it seems I am still missing some basics/understanding. Thanks in advance.
Related
I'm a bit new to Scala
I'm trying to convert the code block starting from if... which looks like Java to something more Scala like (I think a flatmap or case) but I was unable to create the correct result for the function.
Any ideas?
Thanks
override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] = {
def parseResponse(body: JsValue): Option[User] = body.asOpt[User]
if(request.headers.keys.contains("user")) {
val jsonObject: JsValue = Json.parse(request.headers.get("user").get)
val userOptional: Option[User] = parseResponse(jsonObject)
Future.successful(userOptional)
} else {
Future.successful(None)
}
}
Future.successful(request.headers.get("user").flatMap { value =>
val jsonObject: JsValue = Json.parse(value)
val userOptional: Option[User] = parseResponse(jsonObject)
userOptional
})
The conversion from request to User involves three levels of optionality (or "missingness"):
the "user" header could be missing,
the header value could be invalid JSON,
the JSON could not have the right shape to be deserialized into a User.
This multi-level optionality can be elegantly handled with a for-compehension, which will result in None if something is missing at any level, and in Some(user) if everything is good:
def userFromRequest(request: AuthenticatedRequest[A]): Option[User] =
for {
userHeader <- request.headers.get("user") // "user" header may be missing
userJson <- Json.parseOpt(userHeader) // header value may be not valid json
user <- userJson.asOpt[User] // json may be not convertible to user
} yield user
Note that I have taken out the Future from the logic, since this conversion has nothing to do with asynchronous calls.
Then, you can implement the getSubject method by calling the function above:
override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] =
Future.successful(userFromRequest(request))
I am broadcasting a value in Spark Streaming application . But I am not sure how to access that variable in a different class than the class where it was broadcasted.
My code looks as follows:
object AppMain{
def main(args: Array[String]){
//...
val broadcastA = sc.broadcast(a)
//..
lines.foreachRDD(rdd => {
val obj = AppObject1
rdd.filter(p => obj.apply(p))
rdd.count
}
}
object AppObject1: Boolean{
def apply(str: String){
AnotherObject.process(str)
}
}
object AnotherObject{
// I want to use broadcast variable in this object
val B = broadcastA.Value // compilation error here
def process(): Boolean{
//need to use B inside this method
}
}
Can anyone suggest how to access broadcast variable in this case?
There is nothing particularly Spark specific here ignoring possible serialization issues. If you want to use some object it has to be available in the current scope and you can achieve this the same way as usual:
you can define your helpers in a scope where broadcast is already defined:
{
...
val x = sc.broadcast(1)
object Foo {
def foo = x.value
}
...
}
you can use it as a constructor argument:
case class Foo(x: org.apache.spark.broadcast.Broadcast[Int]) {
def foo = x.value
}
...
Foo(sc.broadcast(1)).foo
method argument
case class Foo() {
def foo(x: org.apache.spark.broadcast.Broadcast[Int]) = x.value
}
...
Foo().foo(sc.broadcast(1))
or even mixed-in your helpers like this:
trait Foo {
val x: org.apache.spark.broadcast.Broadcast[Int]
def foo = x.value
}
object Main extends Foo {
val sc = new SparkContext("local", "test", new SparkConf())
val x = sc.broadcast(1)
def main(args: Array[String]) {
sc.parallelize(Seq(None)).map(_ => foo).first
sc.stop
}
}
Just a short take on performance considerations that were introduced earlier.
Options proposed by zero233 are indeed very elegant way of doing this kind of things in Scala. At the same time it is important to understand implications of using certain patters in distributed system.
It is not the best idea to use mixin approach / any logic that uses enclosing class state. Whenever you use a state of enclosing class within lambdas Spark will have to serialize outer object. This is not always true but you'd better off writing safer code than one day accidentally blow up the whole cluster.
Being aware of this, I would personally go for explicit argument passing to the methods as this would not result in outer class serialization (method argument approach).
you can use classes and pass the broadcast variable to classes
your psudo code should look like :
object AppMain{
def main(args: Array[String]){
//...
val broadcastA = sc.broadcast(a)
//..
lines.foreach(rdd => {
val obj = new AppObject1(broadcastA)
rdd.filter(p => obj.apply(p))
rdd.count
})
}
}
class AppObject1(bc : Broadcast[String]){
val anotherObject = new AnotherObject(bc)
def apply(str: String): Boolean ={
anotherObject.process(str)
}
}
class AnotherObject(bc : Broadcast[String]){
// I want to use broadcast variable in this object
def process(str : String): Boolean = {
val a = bc.value
true
//need to use B inside this method
}
}
My controller method:
def postCategory = Action(parse.tolerantText) { request =>
Ok("")
}
and this is my test:
val result = categoryController.postCategory.apply(FakeRequest())
status(result) mustEqual OK //error this line
I have this error:
Error:(63, 14) type mismatch; found :
play.api.libs.streams.Accumulator[akka.util.ByteString,play.api.mvc.Result]
required: scala.concurrent.Future[play.api.mvc.Result]
status(result) mustEqual OK
^
It seem that using a custom parser parse.* makes it returns Accumulator rather than Future[Result]
I'm using play 2.5-RC2
You do should use result.run getting instance of Materializer with Guice
would look like:
import akka.stream.Materializer
//...
def mockApp = new GuiceApplicationBuilder().build()
val mtrlzr = mockApp.injector.instanceOf[Materializer]
val result: Accumulator[ByteString, Result] = controller.accessToken()(FakeRequest())
val runResult: Future[Result] = result.run()(mtrlzr)
You can try with something like this:
val result = categoryController.postCategory.apply(FakeRequest())
status(result.run) must equalTo(OK)
It basically looks like Accumulator has a nice run() method that returns a Future.
Play Framework v 2.1 with scala, I am trying to test my ProductController with invalid call (missing parameters), I am suppose to get BadRequest response..
Controller code is return Ok(Json.toJson(some class..)) or BadRequest(Json.toJson(some class..)) incase something went wrong.
I Defined the test class:
class ProductApplicationTests extends PlaySpecification
with Mockito
with ProductServiceComponent
{
lazy val productService = mock[ProductService]
... other things
def app = FakeApplication(
withoutPlugins = Seq("com.typesafe.plugin.RedisPlugin"),
withGlobal = Some(
new GlobalSettings {
override def getControllerInstance[A](clazz: Class[A]) = clazz match {
case c if c.isAssignableFrom(classOf[ProductController]) => new ProductController(ProductApplicationTests.this).asInstanceOf[A]
case _ => super.getControllerInstance(clazz)
}
}
)
)
def mockStuff = {
productService.addProduct(any[AddProductRequest]) returns
DTOResponse(product.id.get)
productService.updateProduct(any[UpdateProductRequest]) returns
DTOResponse(product.id.get)
productService.deleteProduct(any[DeleteProductRequest]) returns
DTOResponse(product.id.get)
}
step(mockStuff)
"Application" should {
"Get product with no tenant Id" in {running(FakeApplication()) {
val req = FakeRequest(method = "POST", uri = routes.ProductController.getProducts("en_US", "1", "1").url,
headers = FakeHeaders(
Seq("Content-type"->Seq("application/json"))
),
body = None
)
val Some(result) = route(req)
status(result) must equalTo(400)
Problem:
I get error:
Cannot write an instance of None.type to HTTP response. Try to define a Writeable[None.type]
I have followed this post: Play 2 - Scala FakeRequest withJsonBody And i dont understand what im doing wrong..
When i send this code as test it works..
"Get product with valid parameters" in new WithApplication(app) {
val result = route(FakeRequest(GET, "/v1.0/products?lang=en_US&t=1&ids=1,2"))
result must beSome
status(result.get) must equalTo(OK)
contentType(result.get) must beSome.which(_ == "application/json")
contentAsString(result.get) must contain("products")
contentAsString(result.get) must contain("notFoundIds")
}
Thanks for any comment/answers..
By the way my global.scala looks like:
override def onBadRequest(request: RequestHeader, error: String) = {
var errorResponse:ErrorResponse[String] = ErrorResponse(ErrorCode.GeneralError, "Error processing request", 500)
errorResponse.addMessage(error)
Future.successful(BadRequest(Json.toJson(errorResponse)))
}
override def onError(request: RequestHeader, ex: Throwable) =
{
var errorResponse:ErrorResponse[String] = ErrorResponse(ErrorCode.GeneralError, "Error processing request", 500)
errorResponse.addMessage(ex.getMessage)
Future.successful(BadRequest(Json.toJson(errorResponse)))
}
And if i run Get in RestAPI test client i get:
{"code":100,"message":"Error processing request - Missing parameter: t"}
If you take a look at the source for FakeRequest, you'll notice that you need to set the body to AnyContentAsEmpty. On a sidenote, should that first test method be a POST? Your other examples seem to be GETs.
As for your second issue with running Get in RestAPI tet client, the error message seems pretty clear, you need to provide the parameter t as you did in the previous example val result = route(FakeRequest(GET, "/v1.0/products?lang=en_US&t=1&ids=1,2"))
I'm trying to write a Specs2 test case that will test the snippets.
My snippet would look something like this:
class RegisterTest extends Specification {
val testurl = "http:/html/register?username=liftvalues"
val testSession = MockWeb.testS(testurl) { S.session }
def inSession[T](a: => T): T = S.initIfUninitted(testSession) { a }
def is = s2""" example1 $e1 """
val html = <form><input name="username" value="liftvalues"></input></form>
def e1 = {
inSession{
register(html)
}
}
def register(in:NodeSeq):Result = {
val username = S.param("username") //Here we are getting "Empty Value" for the S object.
username === "liftvalues" and UserSchemaTest.registerData("data")
}
}
This test fails since S.param is Empty. What should I do to supply the snippet with a mocked Request?
So far I have looked at Unit Testing Snippets With A Logged In User
and Mocking HTTP Requests, but I do not understand how to achive my goal.
Your code as-is shouldn't even compile, since among other things testSession would return a Box[LiftSession] and S.initIfUninitted requires an unboxed LiftSession. Also, that shouldn't even be needed since MockWeb.testS will initialize the session for you, see here.
I'm not super familiar with Specs2, but I believe something like this should do what you want or at least get you close:
class RegisterTest extends Specification {
val testurl = "http://html/register?username=liftvalues"
val html = <form><input name="username" value="liftvalues"></input></form>
def e1 = register(html)
def register(in:NodeSeq):Boolean = {
val username = S.param("username") //Here we are getting "Empty Value" for the S object.
username === "liftvalues" and UserSchemaTest.registerData("data")
}
MockWeb.testS(testurl) {
s2""" example1 $e1 """
}
}
Everything that gets called from within the MockWeb.testS block should have access to your session and request - so you'd be able to make your method calls normally.
Also, your test also looks wrong, a s2""" will probably throw an error. But, I'm not entirely sure what you are wanting it to do so I couldn't suggest an alternative.