Skip to content

Instantly share code, notes, and snippets.

@heralight
Created March 29, 2012 08:43
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save heralight/2235088 to your computer and use it in GitHub Desktop.
Save heralight/2235088 to your computer and use it in GitHub Desktop.
lift:WebSpec2 + Unit test
package code.mockweb
/*
* Copyright 2011 WorldWide Conferencing, LLC
*
* 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.
*/
import javax.servlet.http.HttpServletRequest
import scala.xml.NodeSeq
import org.specs2.mutable._
import net.liftweb.common
import net.liftweb.common.Empty
import common.{Box,Empty,Full}
import net.liftweb.http
import http._
import net.liftweb.json
import json.JsonAST._
import net.liftweb.mocks
import mocks.MockHttpServletRequest
import net.liftweb.http.LiftRules
import net.liftweb.http.LiftRulesMocker
import net.liftweb.common.Box
import net.liftweb.http.LiftSession
import net.liftweb.json.JsonAST.JValue
import net.liftweb.mocks.MockHttpServletRequest
import net.liftweb.mockweb.MockWeb
import net.liftweb.http.Req
import net.liftweb.http.S
import net.liftweb.common.Full
import org.specs2.execute.Result
/**
* This trait provides Lift-specific extensions to the Specification
* base trait to simplify unit testing of your code. In addition to
* the Scaladoc, Please see the
* source to WebSpecSpec.scala for an example of how to use this. This class
* allows you to optionally configure LiftRules for your spec. LiftRules rules
* are always used, so if you don't want that to happen you can just use MockWeb directly
* in your own custom spec and simply omit any LiftRules setup.
*
* @param boot defines a method that is called prior to
* testing to set up the Lift environment. This is where you'll
* initialize LiftRules, Mapper, etc. The simplest approach
* is to just point this at your Boostrap.boot method.
*/
abstract class WebSpec2(boot : () => Any = () => {}) extends Specification {
/**
* This is our private spec instance of Liftrules. Everything we run will
* use this instance instead of the global instance
*/
private val liftRules = new LiftRules()
LiftRulesMocker.devTestLiftRulesInstance.doWith(liftRules) {
boot()
}
/**
* A class that bridges between the description string
* and classes that provide the Lift-specific wrapping
* of the Expectation code. The method names here
* should be self-explanatory. For methods that take a
* Box[LiftSession], an Empty Box will result in the
* creation of a new Session.
*/
class WebSpecBridge (description : String) {
def withSFor (url : String, session : Box[LiftSession] = Empty, contextPath : String = "") =
new SessionSpecification(description, url, session, contextPath)
def withSFor (url : String, session : LiftSession) : SessionSpecification =
new SessionSpecification(description, url, Box.!!(session), "")
def withSFor (req : HttpServletRequest) =
new SessionSpecification(description, req, Empty)
def withSFor (req : HttpServletRequest, session : Box[LiftSession]) =
new SessionSpecification(description, req, session)
def withSFor (req : HttpServletRequest, session : LiftSession) =
new SessionSpecification(description, req, Box.!!(session))
def withReqFor (url : String, contextPath : String = "") =
new ReqSpecification(description, url, contextPath)
def withReqFor (req : HttpServletRequest) =
new ReqSpecification(description, req)
def withTemplateFor (url : String, session : Box[LiftSession] = Empty, contextPath : String = "") =
new TemplateSpecification(description, url, session, contextPath)
def withTemplateFor (url : String, session : LiftSession) =
new TemplateSpecification(description, url, Box.!!(session), "")
def withTemplateFor (req : HttpServletRequest) =
new TemplateSpecification(description, req, Empty)
def withTemplateFor (req : HttpServletRequest, session : Box[LiftSession]) =
new TemplateSpecification(description, req, session)
def withTemplateFor (req : HttpServletRequest, session : LiftSession) =
new TemplateSpecification(description, req, Box.!!(session))
}
/**
* Converts a String description into a WebSpecBridge that can
* then be used to set up either an S or Req instance.
*/
implicit def strToWebSpecBridge (description : String) =
new WebSpecBridge(description)
/**
* A comon trait to provide utility methods for mutating the
* underlying HttpServletRequest.
*/
trait ModifiableRequest [T <: ModifiableRequest[T]] {
// Make sure that our return values are for the supertype, not ModifiableRequest
self : T =>
val req : HttpServletRequest
/**
* Modifies the request to POST the given request body text. Optionally,
* you can set the content type (defaults to "text/plain")
*/
def withPost (text : String, contentType : String = "text/plain") : T =
withMods { mockReq =>
mockReq.body = text
mockReq.contentType = contentType
mockReq.method = "POST"
}
/**
* Modifies the request to POST the given request body JSON.
*/
def withPost (jval : JValue) = withMods { mockReq =>
mockReq.body = jval
mockReq.method = "POST"
}
/**
* Modifies the request to POST the given request body XML.
*/
def withPost (node : NodeSeq) = withMods { mockReq =>
mockReq.body = node
mockReq.method = "POST"
}
/**
* Modifies the request to PUT the given request body text. Optionally,
* you can set the content type (defaults to "text/plain")
*/
def withPut (text : String, contentType : String = "text/plain") =
withMods { mockReq =>
mockReq.body = text
mockReq.contentType = contentType
mockReq.method = "PUT"
}
/**
* Modifies the request to PUT the given request body JSON.
*/
def withPut (jval : JValue) = withMods { mockReq =>
mockReq.body = jval
mockReq.method = "PUT"
}
/**
* Modifies the request to PUT the given request body XML.
*/
def withPut (node : NodeSeq) = withMods { mockReq =>
mockReq.body = node
mockReq.method = "PUT"
}
/**
* Allows you to specify your own modification function for the servlet request
* prior to initialization.
*/
def withMods (f : MockHttpServletRequest => Unit) : T = req match {
case r : MockHttpServletRequest => f(r); this
case _ => throw new IllegalArgumentException("We can only mutate MockHttpServletRequest instances")
}
}
/**
* This class provides a wrapper to test methods that require an
* initialized S.
*/
class SessionSpecification (description : String,
val req : HttpServletRequest,
session : Box[LiftSession]) extends ModifiableRequest[SessionSpecification] {
def this (description : String, url : String, session : Box[LiftSession], contextPath : String) =
this(description, new MockHttpServletRequest(url, contextPath), session)
def in [T <% Result](expectations : => T) = {
// val example = exampleContainer.createExample(description)
// if (sequential) example.setSequential()
exampleFactory.newExample (description, {
LiftRulesMocker.devTestLiftRulesInstance.doWith(liftRules) {
MockWeb.useLiftRules.doWith(true) {
MockWeb.testS(req, session) {
expectations
}
}
}
})
}
}
/**
* This class provides a wrapper to test methods that require an
* initialized Req.
*/
class ReqSpecification (description : String,
val req : HttpServletRequest) extends ModifiableRequest[ReqSpecification] {
def this (description : String, url : String, contextPath : String) =
this(description, new MockHttpServletRequest(url, contextPath))
def in [T <% Result](expectations : Req => T) = {
// val example = exampleFactory.newExample (description, expectations)// exampleContainer.createExample(description)
// if (sequential) example.setSequential()
exampleFactory.newExample (description, {
LiftRulesMocker.devTestLiftRulesInstance.doWith(liftRules) {
MockWeb.useLiftRules.doWith(true) {
MockWeb.testReq(req)(expectations)
}
}
})
}
}
/**
* This class provides a wrapper to test methods that require
* a processed template.
*/
class TemplateSpecification (description : String,
val req : HttpServletRequest,
session : Box[LiftSession]) extends ModifiableRequest[TemplateSpecification] {
def this (description : String, url : String, session : Box[LiftSession], contextPath : String) =
this(description, new MockHttpServletRequest(url, contextPath), session)
def in [T <% Result](expectations : Box[NodeSeq] => T) = {
// val example = exampleContainer.createExample(description)
// if (sequential) example.setSequential()
exampleFactory.newExample (description, {
LiftRulesMocker.devTestLiftRulesInstance.doWith(liftRules) {
MockWeb.useLiftRules.doWith(true) {
MockWeb.testS(req, session) {
S.request match {
case Full(sReq) => expectations(S.runTemplate(sReq.path.partPath))
case other => failure("Error: withTemplateFor call did not result in " +
"request initialization (S.request = " + other + ")")
}
}
}
}
})
}
}
}
package code.mockweb
/*
* Copyright 2011 WorldWide Conferencing, LLC
*
* 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.
*/
import net.liftweb._
import mockweb.MockWeb
import scala.xml.NodeSeq
import common.{Box,Empty,Full}
import http._
import http.rest._
import json._
import json.JsonDSL._
import mocks.MockHttpServletRequest
/**
* This only exists to keep the WebSpecSpec clean. Normally,
* you could just use "() => bootstrap.Boot.boot".
*/
object WebSpecSpecBoot {
def boot() {
println("WebSpecSpec Booting up")
// Add this so that withTemplateFor test works
LiftRules.addToPackages("net.liftweb.mockweb")
LiftRules.statelessRewrite.append {
case RewriteRequest(ParsePath(List("test", "stateless"), _, _, _), _, _) => {
RewriteResponse(List("stateless", "works"))
}
}
LiftRules.statefulRewrite.append {
case RewriteRequest(ParsePath(List("test", "stateful"), _, _, _), _, _) => {
RewriteResponse(List("stateful", "works"))
}
}
println("WebSpecSpec Boot complete")
}
}
/**
* A test RestHelper to show usage.
*/
object WebSpecSpecRest extends RestHelper {
serve {
case "api" :: "info" :: Nil JsonGet req => {
("version" -> "1.0") ~ ("name" -> "WebSpec")
}
}
}
/**
* This spec does double duty as both a spec against the
* WebSpec trait as well as an example of how to use it.
*/
class WebSpec2Spec extends WebSpec2(WebSpecSpecBoot.boot _) {
"WebSpec" should {
args(sequential=true) // This is important for using SessionVars, etc.
val testUrl = "http://foo.com/test/stateless"
val testReq =
new MockHttpServletRequest("http://foo.com/test/this?foo=bar", "/test")
// Create a new session for use in the tests
val testSession = MockWeb.testS(testUrl) {
S.session
}
object TestVar extends SessionVar[String]("Empty")
"properly set up S with a String url" withSFor(testUrl) in {
S.request match {
case Full(req) => req.path.partPath must_== List("stateless", "works")
case _ => failure("No request in S")
}
}
"properly set up S with a String url and session" withSFor(testUrl, testSession) in {
TestVar("foo!")
TestVar.is must_== "foo!"
}
"properly re-use a provided session" withSFor(testUrl, testSession) in {
TestVar.is must_== "foo!"
}
"properly set up S with a HttpServletRequest" withSFor(testReq) in {
S.uri must_== "/this"
S.param("foo") must_== Full("bar")
}
"properly set up a Req with a String url" withReqFor(testUrl) in {
_.path.partPath must_== List("stateless", "works")
}
"properly set up a Req with a String url and context path" withReqFor(testUrl, "/test") in {
_.path.partPath must_== List("stateless")
}
"properly set up a Req with a HttpServletRequest" withReqFor(testReq) in {
_.uri must_== "/this"
}
"properly set a plain text body" withReqFor(testUrl) withPost("This is a test") in {
req =>
req.contentType must_== Full("text/plain")
req.post_? must_== true
req.body match {
case Full(body) => (new String(body)) must_== "This is a test"
case _ => failure("No body set")
}
}
"properly set a JSON body" withReqFor(testUrl) withPut(("name" -> "Joe")) in {
req =>
req.json_? must_== true
req.put_? must_== true
req.json match {
case Full(jval) => jval must_== JObject(List(JField("name", JString("Joe"))))
case _ => failure("No body set")
}
}
"properly set an XML body" withSFor(testUrl) withPost(<test/>) in {
S.request match {
case Full(req) => {
req.xml_? must_== true
req.post_? must_== true
req.xml must_== Full(<test/>)
}
case _ => failure("No request found in S")
}
}
"properly mutate the request" withSFor(testUrl) withMods(_.contentType = "application/xml") in {
(S.request.map(_.xml_?) openOr false) must_== true
}
"process a JSON RestHelper Request" withReqFor("http://foo.com/api/info.json") in { req =>
(WebSpecSpecRest(req)() match {
case Full(JsonResponse(_, _, _, 200)) => true
case other => failure("Invalid response : " + other); false
}) must_== true
}
// "properly process a template" withTemplateFor("http://foo.com/net/liftweb/mockweb/webspecspectemplate") in ({
// case Full(template) => template.toString.contains("Hello, WebSpec!") must_== true
// case other => {
// failure("Error on template : " + other)
// }
// } : PartialFunction[Box[NodeSeq],Unit])
}
}
@heralight
Copy link
Author

Work with

 <dependency>
            <groupId>org.specs2</groupId>
            <artifactId>specs2_2.9.1</artifactId>
            <version>1.8.2</version>
            <scope>test</scope>
 </dependency>

or
val spec2Version = "1.8.2"
val specs2 = "org.specs2" %% "specs2" % spec2Version %"test->default" // For specs2.org tests

Intellij 11.1 min.

@ricsirigu
Copy link

Where does "exampleFactory" come from?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment