Skip to content

Commit

Permalink
Merge 6ff1102 into 17ca180
Browse files Browse the repository at this point in the history
  • Loading branch information
savaki committed Nov 6, 2013
2 parents 17ca180 + 6ff1102 commit 3558e16
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/main/scala/com/twitter/finatra/test/FlatSpecHelper.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/**
* Copyright (C) 2012 Twitter Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twitter.finatra.test

import org.scalatest.FlatSpec
Expand Down
152 changes: 152 additions & 0 deletions src/main/scala/com/twitter/finatra/test/MockApp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* Copyright (C) 2012 Twitter Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twitter.finatra.test

import com.twitter.finatra._
import org.jboss.netty.handler.codec.http.HttpMethod
import com.twitter.util.{Await, Future}
import com.twitter.finagle.http.Response
import com.twitter.finagle.http.{Request => FinagleRequest, Response => FinagleResponse}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import scala.collection.Map
import scala.collection.JavaConverters._
import java.util
import org.jboss.netty.buffer.ChannelBuffers
import java.net.URLEncoder
import com.twitter.finagle.Service

class MockApp(service: Service[FinagleRequest, FinagleResponse]) {
def buildRequest(method: HttpMethod, path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): FinagleRequest = {

// ensure that we don't have both params and body
if (body != null && !params.isEmpty) {
throw new RuntimeException("unable to build request. You can specify EITHER params OR a body, but not BOTH.")
}

val request = FinagleRequest(path, params.toList: _*)
request.httpRequest.setMethod(method)

// apply body
for (buffer <- toByteArray(body)) {
request.setContent(ChannelBuffers.wrappedBuffer(buffer))
request.addHeader("Content-Length", buffer.length.toString)
}

// add headers
headers.foreach {
header => request.httpRequest.setHeader(header._1, header._2)
}

request
}

def execute(method: HttpMethod, path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): MockResponse = {
// Execute the test
val request = buildRequest(method, path, params, headers, body)
val response: Future[Response] = service(request)
new MockResponse(Await.result(response))
}

/**
* helper method to url encode a value
*/
private[finatra] def encode(value: AnyRef) = URLEncoder.encode(value.toString, "UTF-8")

/**
* encodes a map into a url form-encoded string
*/
private[finatra] def encodeFormData(params: Map[AnyRef, AnyRef]): String = {
params.map {
case (key, value) => encode(key.toString) + "=" + encode(value.toString)
}.mkString("&")
}

/**
* intelligently convert an AnyRef into a Array[Byte] using the following rules:
*
* <ul>
* <li>value: String => value.getBytes</li>
* <li>value: Array[Byte] => value</li>
* <li>value: Map[_, _] => url-encoded form data</li>
* <li>value: util.Map[_, _] => url-encoded form data</li>
* <li>value: AnyRef => uses jackson to attempt to convert this to a json string</li>
* </ul>
*/
private[finatra] def toByteArray(body: AnyRef): Option[Array[Byte]] = {
import MockApp._

val buffer: Array[Byte] = body match {
case null => null
case value: String => value.getBytes
case value: Array[Byte] => value
case value: util.Map[_, _] => encodeFormData(value.asInstanceOf[util.Map[AnyRef, AnyRef]].asScala.toMap).getBytes
case value: Map[_, _] => encodeFormData(value.asInstanceOf[Map[AnyRef, AnyRef]]).getBytes
case anythingElse => mapper.writeValueAsBytes(anythingElse)
}
Option(buffer)
}

def get(path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): MockResponse = {
execute(HttpMethod.GET, path, params, headers, body)
}

def post(path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): MockResponse = {
execute(HttpMethod.POST, path, params, headers, body)
}

def put(path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): MockResponse = {
execute(HttpMethod.PUT, path, params, headers, body)
}

def delete(path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): MockResponse = {
execute(HttpMethod.DELETE, path, params, headers, body)
}

def head(path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): MockResponse = {
execute(HttpMethod.HEAD, path, params, headers, body)
}

def patch(path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): MockResponse = {
execute(HttpMethod.PATCH, path, params, headers, body)
}

def options(path: String, params: Map[String, String] = Map(), headers: Map[String, String] = Map(), body: AnyRef = null): MockResponse = {
execute(HttpMethod.OPTIONS, path, params, headers, body)
}
}

object MockApp {
val mapper = {
val m = new ObjectMapper()
m.registerModule(DefaultScalaModule)
m
}

def apply(controller: Controller): MockApp = {
val server = new FinatraServer
server.register(controller)
apply(server)
}

def apply(server: FinatraServer): MockApp = {
val appService = new AppService(server.controllers)
val service = server.allFilters(appService)

new MockApp(service)
}
}

76 changes: 76 additions & 0 deletions src/test/scala/com/twitter/finatra/test/MockAppSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Copyright (C) 2012 Twitter Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twitter.finatra.test

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import com.twitter.finatra.Controller
import scala.collection.JavaConverters._
import org.jboss.netty.handler.codec.http.HttpMethod
import com.twitter.finagle.http.Request

class MockAppSpec extends FlatSpec with ShouldMatchers {
val server = MockApp(new Controller)

"#toByteArray" should "directly convert String to Array[Byte]" in {
val value = "hello world"
server.toByteArray(value).get should be(value.getBytes)
}

it should "also directly convert Array[Byte] to Array[Byte]" in {
val value = "hello world".getBytes
server.toByteArray(value).get should be(value)
}

it should "convert Map[String, String] to url-encoded form data" in {
val value = Map("hello" -> "world")
server.toByteArray(value).get should be("hello=world".getBytes)
}

it should "convert util.Map[String, String] to url-encoded form data" in {
val value = Map("hello" -> "world").asJava
server.toByteArray(value).get should be("hello=world".getBytes)
}

it should "convert null to None" in {
server.toByteArray(null) should be(None)
}

it should "attempt to convert other objects to a json equivalent" in {
val sample = Sample("matt", "matt@does-not-exist.com")
server.toByteArray(sample).get should be( """{"name":"matt","email":"matt@does-not-exist.com"}""".getBytes)
}

"#buildRequest" should "apply body if present" in {
val sample = Sample("matt", "matt@does-not-exist.com")

// When
val request: Request = server.buildRequest(HttpMethod.POST, "/", body = sample)

// Then
request.contentString should be(MockApp.mapper.writeValueAsString(sample))
}

it should "not allow both params AND a non-null body in the same request" in {
val sample = Sample("matt", "matt@does-not-exist.com")

evaluating {
server.buildRequest(HttpMethod.POST, "/", params = Map("hello" -> "world"), body = sample)
} should produce[RuntimeException]
}

case class Sample(name: String, email: String)
}
40 changes: 40 additions & 0 deletions src/test/scala/com/twitter/finatra/test/SampleSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (C) 2012 Twitter Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twitter.finatra.test

import org.scalatest.matchers.ShouldMatchers
import org.scalatest.FlatSpec
import com.twitter.finatra.Controller

class SampleSpec extends FlatSpec with ShouldMatchers {

class SampleController extends Controller {
get("/testing") {
request => render.body("hello world").status(200).toFuture
}
}

"Sample Use Case" should "allow us to instantiate separate controller for each test" in {
val app: MockApp = MockApp(new SampleController)

// When
val response = app.get("/testing")

// Then
response.code should be(200)
response.body should be("hello world")
}
}

0 comments on commit 3558e16

Please sign in to comment.