I have a method in my controller that I'd like to call directly. It takes in a POSTed form, validates it, then returns something. I want to test this directly - i.e. not go through the routes helper.
This is my Form code (FormFields is just a case class)
val searchForm = Form(
mapping(
"foo" -> nonEmptyText,
"filter" -> optional(text).verifying("Filter text must be 'published' or 'unpublished'",
x => x.isEmpty || x.get == "published" || x.get == "unpublished")
)(FormFields.apply)(FormFields.unapply)
)
This is my controller call.
def doThings() = IsAuthenticated {
username => implicit request => {
searchForm.bindFromRequest().fold(
formWithErrors => BadRequest(s"Incorrect data: ${formWithErrors.errors.map(x => s"${x.key} ${x.message}").mkString}."),
form => {
OK("Test text here")
}
)
}
}
If I call this via my routes file, as below - This works as expected. Form gets posted, verified, returns OK("Test...") as expected.
ie. The below works (using Specs2)
val request = FakeRequest(POST, "notarealurl")
.withFormUrlEncodedBody(
"filter" -> "published",
"foo" -> "Test"
).withSession("email" -> "testuser")
val Some(result) = route(request)
status(result) must equalTo(OK)
However, whatever I try to call the method directly fails - The failure happens on the form validation step. It tells me that "foo" is missing a value when I run the unit test. This is how I'm attempting to do this.
val request = FakeRequest()
.withFormUrlEncodedBody(
"filter" -> "published",
"foo" -> "Test"
).withSession("email" -> "testuser")
//val Some(result) = route(request)
val result = Search.searchProducts()(request)
println(contentAsString(result))
status(result) must equalTo(OK)
The text printed is "Incorrect search: foo error.required." I think I'm not doing the call properly, but I don't know where I'm going wrong.
Note : The code here represents my problem but has been cut down to just illustrate the issue.
I mimicked your logic, and it runs fine. I replaced some of your code with copy-paste from Play documentation just to keep it minimal. I tested it on top of a setup I'm working on now
so you'll see artifacts foreign to the default Play setup. This setup is more or less identical to the one described in the article I linked originally. I wouldn't know how to get more direct than this:
In controller:
import play.api.data._
import play.api.data.Forms._
case class UserData(name: String, age: Int)
val userFormConstraints2 = Form(
mapping(
"name" -> nonEmptyText,
"age" -> number(min = 0, max = 100)
)(UserData.apply)(UserData.unapply)
)
def test = Action {
implicit request => {
userFormConstraints2.bindFromRequest().fold(
formWithErrors => BadRequest("bad"),
userData => {
Ok(userData.name + userData.age)
}
)
}
}
Test:
class TempSpec extends Specification with MyHelpers {
"1" can {
"direct access to controller while posting" in new TestServer {
// `in new TestServer` spawns dependencies (`server`)
val controller = new controllers.Kalva(server)
// I instantiate the controller passing the dependency
val request = FakeRequest(POST, "bla")
.withFormUrlEncodedBody(
"name" -> "Richard",
"age" -> "1"
)
val result = controller.test()(request)
status(result) must equalTo(OK)
contentAsString(result) must contain("Richard");
val request_bad = FakeRequest(POST, "bla")
.withFormUrlEncodedBody(
"name" -> "",
"age" -> "-1"
)
val result_bad = controller.test()(request_bad)
status(result_bad) must equalTo(400)
contentAsString(result_bad) must contain("bad");
}
}
}
Global.scala:
object Global extends GlobalSettings {
private lazy val injector = Guice.createInjector(new TestModule)
override def getControllerInstance[A](controller: Class[A]) = {
injector.getInstance(controller)
}
}
TestModule:
import com.google.inject._
import com.tzavellas.sse.guice.ScalaModule
class TestModule extends ScalaModule {
def configure() {
#Provides
def getServer:Server = {
...
}
}
}
Within routes file:
POST /bla #controllers.Kalva.test
// the `#` prefix is needed because of how we fetch controllers
Original answer below:
class TranslateSpec extends Specification {
"Translate" should {
// The normal Play! way
"accept a name, and return a proper greeting" in {
running(FakeApplication()) {
val translated = route(FakeRequest(GET, "/greet/Barney")).get
status(translated) must equalTo(OK)
contentType(translated) must beSome.which(_ == "text/html")
contentAsString(translated) must contain ("Barney")
}
}
// Providing a fake Global, to explitly mock out the injector
object FakeTranslatorGlobal extends play.api.GlobalSettings {
override def getControllerInstance[A](clazz: Class[A]) = {
new Translate(new FakeTranslator).asInstanceOf[A]
}
}
"accept a name, and return a proper greeting (explicitly mocking module)" in {
running(FakeApplication(withGlobal = Some(FakeTranslatorGlobal))) {
val home = route(FakeRequest(GET, "/greet/Barney")).get
contentAsString(home) must contain ("Hello Barney")
}
}
// Calling the controller directly, without creating a new FakeApplication
// (This is the fastest)
"accept a name, and return a proper greeting (controller directly, no FakeApplication!)" in {
val controller = new Translate(new FakeTranslator)
val result = controller.greet(name = "Barney")(FakeRequest())
contentAsString(result) must contain ("Hello Barney")
}
}
}
The code above is quite self-descriptive and it shows the default testing workflow and how it can be improved with Dependency Injection. It's a quote from this article.
This particular excerpt is from the "Why should I use DI with Play?" section. The article is about setting up Google Guice with Play2 and the possibilities it opens up. It's a practical read.
As you can see above, "The normal Play! way" is fine, but by embracing DI you can get away with so much more in your tests (and development in general of course).
As is described in the article, using Guice with Play involves making minor changes to Play's default set-up and it's well worth it. I've been doing it this way for a long time now, and haven't looked back.
Related
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.
My function under test returns None or Some(ObjectOfSignupEmail). In my test case, I want to match that the returned value is Some(ArgumentMatchers.any[SignupEmail]) but I get error
Expected :Some(null)
Actual :Some(SignupEmail(Welcome,Test<mailrobot#test.com>,<a href=https://localhost:9000/test/ws/users/signup/11111111-1111-1111-1111-111111111111>Click here to verify email</a>))
If I change the code to signupEmailOption mustBe Some(expectedAnswer) where expectedAnswer is an instance of SignupEmail then the test passes.
Why ArgumentMatchers.any didn't work inside Some?
This doesn't work
"createEmailMessageForUserToken for all correct parameters" should {
"return Some(email)" in {
val mailConfig = Map("signupUrl"-> "/test/ws/users/signup/",
"signupFrom"->"Test<mailrobot#test.com>",
"signupReply"->"Test<noreply#test.com>",
"signupSubject"->"Welcome")
val mailerConfig = Map(
"host" -> "localhost", // (mandatory). The domain of mail server i.e. the server is responsible for sending/receiving emails for this domain
"port" -> "9000",
"tlsRequired" -> "yes"
)
val newConfig = Map("mail"->mailConfig,
"play.mailer"->mailerConfig)
val newConfiguration = Configuration.from(newConfig)
val testEnv = new TestEnv(newConfiguration)
val signupEmailOption:Option[SignupEmail] = testEnv.controller.createEmailMessageForUserToken(testEnv.userToken)
signupEmailOption mustBe Some(ArgumentMatchers.any(SignupEmail.getClass()))
}
}
This works
"createEmailMessageForUserToken for all correct parameters" should {
"return Some(email)" in {
val mailConfig = Map("signupUrl"-> "/test/ws/users/signup/",
"signupFrom"->"Test<mailrobot#test.com>",
"signupReply"->"Test<noreply#test.com>",
"signupSubject"->"Welcome")
val mailerConfig = Map(
"host" -> "localhost", // (mandatory). The domain of mail server i.e. the server is responsible for sending/receiving emails for this domain
"port" -> "9000",
"tlsRequired" -> "yes"
)
val newConfig = Map("mail"->mailConfig,
"play.mailer"->mailerConfig)
val newConfiguration = Configuration.from(newConfig)
val testEnv = new TestEnv(newConfiguration)
val url = "https://" + mailerConfig("host") + ":" + mailerConfig("port") + mailConfig("signupUrl") + testEnv.userToken.tokenId
val html =s"<a href=${url}>Click here to verify email</a>"
//println("html is "+html)
val expectedAnswer = SignupEmail(mailConfig("signupSubject"),mailConfig("signupFrom"),html)
println("expected answer would be "+expectedAnswer)
val signupEmailOption:Option[SignupEmail] = testEnv.controller.createEmailMessageForUserToken(testEnv.userToken)
signupEmailOption mustBe Some(expectedAnswer)
// signupEmailOption mustBe Some(ArgumentMatchers.any(SignupEmail.getClass()))
}
}
You have to use Scalatest matchers instead of Mockito matchers for what you want to do
You are mixing concepts, mockito matchers are meant to be used in arguments of stubbed mock methods, if you want to assert the result of an invocation to your test object you have to use the matchers provided by your test framework (Scalatest in your case for what I can see), so basically check this page for the docs of mustBe and Options.
A hint: if you want to check the type of whatever is inside the option, you can use a partial function matcher and write something like
signupEmailOption should matchPattern { case Some(_: SignupEmail) => }
A few options.
signupEmailOption should not be None
signupEmailOption should not be empty
signupEmailOption shouldBe defined
signupEmailOption should matchPattern { case Some(_) => }
inside(signupEmailOption) { case Some(_) => }
These are all equivalent.
But what you are doing - signupEmailOption shouldBe Some(expectedAnswer) is actually the best option of all. It is the right thing to do here. Just keep it like that.
Note: should* and must* assertions are pretty much the same thing, they just depend on which DSL trait you mix in.
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 have these case class
case class Blog(id:Long, author:User, other stuff...)
case class Comment(id:Long, blog:Blog, comment:String)
and a form on the client side that submits the data
blog_id:"5"
comment:"wasssup"
I'm writing some simple code to let a user add a comment to a blog.
The user is logged in so the his user_id is not needed from the client side, we know who he is...
I would like to bind the blog_id to a Blog object loaded from db, and if it doesn't exist show an error.
The examples on play framework docs are not helpful.
They only show mappings for forms that represent a single Object and all of its fields.
Here I'm representing a tuple of a (b:Blog, comment:String) and for the Blog I'm only supplying it's id.
I'd like to have a mapping that would provide me with the conversion + validation + error messages, so i can write something like:
val form = Form(
tuple(
"blog_id" -> blogMapping,
"comment" -> nonEmptyText
)
)
form.bindFromRequest().fold(...
formWithErrors => {...
}, {
case (blog, comment) => {do some db stuff to create the comment}
...
The "blogMapping" wlil work like other mappings, it will bind the posted data to an object, in our case a blog loaded from db, and in case it's not successful it will provide an error that we can use on the formWithErrors => clause.
I'm not sure how to accomplish this, the docs are kinda lacking here...
any help is appreciated!
I ended up looking at how playframwork's current bindings look like and implementing something similar, but for Blog:
implicit def blogFromLongFormat: Formatter[Blog] = new Formatter[Blog] {
override val format = Some(("Blog does not exist", Nil))
def bind(key: String, data: Map[String, String]) = {
scala.util.control.Exception.allCatch[Long] either {
data.get(key).map(s => {
val blog_id = s.toLong
val blog = Daos.blogDao.retrieve(blog_id)
blog.map(Right(_)).getOrElse(Left(Seq(FormError(key, "Blog not found", Nil))))
}).get
} match {
case Right(e:Either[Seq[FormError],Blog]) => e
case Left(exception) => Left(Seq(FormError(key, "Invalid Blog Id", Nil)))
case _ => Left(Seq(FormError(key, "Error in form submission", Nil)))
}
}
def unbind(key: String, value: Blog) = Map(key -> value.id.toString)
}
val blogFromLongMapping: Mapping[Blog] = Forms.of[Blog]
To me, this doesn't really look like a binding problem.
The issue is around the Model-View-Controller split. Binding is a Controller activity, and it's about binding web data (from your View) to your data model (for use by the Model). Querying the data, on the other hand, would very much be handled by the Model.
So, the standard way to do this would be something like the following:
// Defined in the model somewhere
def lookupBlog(id: Long): Option[Blog] = ???
// Defined in your controllers
val boundForm = form.bindFromRequest()
val blogOption = boundForm.value.flatMap {
case (id, comment) => lookupBlog(id)
}
blogOption match {
case Some(blog) => ??? // If the blog is found
case None => ??? // If the blog is not found
}
However, if you are determined to handle database lookup in your binding (I'd strongly advise against this, as it will lead to spaghetti code in the long run), try something like the following:
class BlogMapping(val key: String = "") extends Mapping[Blog] {
val constraints = Nil
val mappings = Seq(this)
def bind(data: Map[String, String]) = {
val blogOpt = for {blog <- data.get(key)
blog_id = blog.toLong
blog <- lookupBlog(blog_id)} yield blog
blogOpt match {
case Some(blog) => Right(blog)
case None => Left(Seq(FormError(key, "Blog not found")))
}
}
def unbind(blog: Blog) = (Map(key -> blog.id.toString), Nil)
def withPrefix(prefix: String) = {
new BlogMapping(prefix + key)
}
def verifying(constraints: Constraint[Blog]*) = {
WrappedMapping[Blog, Blog](this, x => x, x => x, constraints)
}
}
val blogMapping = new BlogMapping()
val newform = Form(
tuple(
"blog_id" -> blogMapping,
"comment" -> nonEmptyText
)
)
// Example usage
val newBoundForm = newform.bindFromRequest()
val newBoundBlog = newBoundForm.get
The main thing we've done is to create a custom Mapping subclass. This can be a good idea under some circumstances, but I'd still recommend the first approach.
You can do it all in the form definition.
I have made some simple scala classes and objects from your example.
models/Blog.scala
package models
/**
* #author maba, 2013-04-10
*/
case class User(id:Long)
case class Blog(id:Long, author:User)
case class Comment(id:Long, blog:Blog, comment:String)
object Blog {
def findById(id: Long): Option[Blog] = {
Some(Blog(id, User(1L)))
}
}
object Comment {
def create(comment: Comment) {
// Save to DB
}
}
controllers/Comments.scala
package controllers
import play.api.mvc.{Action, Controller}
import play.api.data.Form
import play.api.data.Forms._
import models.{Comment, Blog}
/**
* #author maba, 2013-04-10
*/
object Comments extends Controller {
val form = Form(
mapping(
"comment" -> nonEmptyText,
"blog" -> mapping(
"id" -> longNumber
)(
(blogId) => {
Blog.findById(blogId)
}
)(
(blog: Option[Blog]) => Option(blog.get.id)
).verifying("The blog does not exist.", blog => blog.isDefined)
)(
(comment, blog) => {
// blog.get is always possible since it has already been validated
Comment(1L, blog.get, comment)
}
)(
(comment: Comment) => Option(comment.comment, Some(comment.blog))
)
)
def index = Action { implicit request =>
form.bindFromRequest.fold(
formWithErrors => BadRequest,
comment => {
Comment.create(comment)
Ok
}
)
}
}
I am making a simple snippet that should pass a Box[String] with the requests user-agent to a helper class that passes back the css classes that should be added to the html element. I am doing this since it seems tricky to get Lift to supply a html respons with conditional comments like those in html5boilerplate. This is what I have now and it works:
class LiftBoilerplate {
def render = "html [class+]" #> getClassForUserAgent(S.request)
private def getClassForUserAgent(request:Box[Req]) = request match {
case Full(r) => LiftBoilerplateHelper.getHtmlClass(r.userAgent)
case _ => ""
}
}
My problem is that I'd like to write a unit test for this like:
object LiftBoilerplateSpecs extends Specification {
val session = new LiftSession("", randomString(20), Empty)
"LiftBoilerplate" should {
"add 'no-js' to the class of an html tag element" in {
val snippet = new LiftBoilerplate
val result = snippet.render(<html><head></head><body>test</body></html>)
result must ==/(<html class="no-js"><head></head><body>test</body></html>)
}
}
}
This test fails since S.request is Empty. What should I do to supply the snippet with a mocked request with a userAgent in it?
So far I have looked at http://www.assembla.com/spaces/liftweb/wiki/Unit_Testing_Snippets_With_A_Logged_In_User
and
http://www.assembla.com/spaces/liftweb/wiki/Mocking_HTTP_Requests
but I do not understand how to achive my goal.
To make the request and apply it automatically in each test example you will need to use the Trait AroundExample to wrap each test in a S.init call:
object LiftBoilerplateSpecs extends Specification with AroundExample {
val session = new LiftSession("", randomString(20), Empty)
def makeReq = {
val mockRequest = new MockHttpServletRequest("http://localhost")
mockRequest.headers = Map("User-Agent" -> List("Safari"))
new Req(Req.NilPath, "", GetRequest, Empty, new HTTPRequestServlet(mockRequest, null),
System.nanoTime, System.nanoTime, false,
() => ParamCalcInfo(Nil, Map(), Nil, Empty), Map())
}
def around[T <% Result](t: => T) = S.init(makeReq, session)(t)
"LiftBoilerplate" should {
"add 'no-js' to the class of an html tag element" in {
val snippet = new LiftBoilerplate
val result = snippet.render(<html><head></head><body>test</body></html>)
result must ==/(<html class="no-js"><head></head><body>test</body></html>)
}
}
}