Version 8, last updated by heralight at 31 Mar 12:18 UTC
Unit Testing Snippets With A Logged In User
Thanks to Bill Venners and David Pollak for help with this issue.
Overview
It is common to want to write unit tests for your lift snippets. This page is meant to show you how to test within a context of a session. For instance, say that you have written snippet code that depends upon a user to be logged in. What do you need to do to get the session state enabled so that you have a valid user logged in that you can operate against?
Example code for JUnit
package com.foo
import com.foo.User
import net.liftweb.http.{S, LiftSession}
import net.liftweb.mapper.BaseMetaMapper
import net.liftweb.util._
import net.liftweb.common._
import junit.framework.TestCase
// My snippet probably which lives in another file
class MySnippet {
def showNames(xhtml : Group) : NodeSeq = {
val : User = User.currentUser.open_!
user.children.flatMap(child => bind("f", xhtml,
"name" => child.name
))
}
}
class MySnippetTest extends TestCase {
val session : LiftSession = new LiftSession("", StringHelpers.randomString(20), Empty)
override def setUp : Unit = {
// set up your db here
}
override def tearDown : Unit = {
// tear down your db here
}
private GetNames() = {
val xml = <xml:group><f:name>Name</f:name></xml:group>
val snippet = new MySnippet()
val output = snippet.showNames(xml)
// Do verification of data returned; assert if something is amiss
()
}
def testValue() = {
// Initialize session state if it is not already
S.initIfUninitted(session) {
// Create and log-in the user
val user : User = User.create
user.firstName("XXX")
user.lastName("YYYY")
user.save
User.logUserIn(user)
// Call the test to run
GetNames()
()
}
()
}
}Example code for ScalaTest
One way to do this in ScalaTest 1.0 is to override withFixture, which takes a NoArgTest function. ScalaTest will pass each test as a function to withFixture, which is responsible for running the test. The default implementation of withFixture just invokes the test function. If you override withFixture, you can perform setup before invoking the test function, then in a finally clause, perform any needed tear down. Because each test will be executed inside withFixture, you can use withFixture to initialize the session anew for each test using S.initIfUninitted. Here’s an example that uses a FlatSpec:
package com.foo
import com.foo.User
import net.liftweb.http.{S, LiftSession}
import net.liftweb.mapper.BaseMetaMapper
import net.liftweb.util._
import net.liftweb.common._
import org.scalatest.FlatSpec
import org.scalatest.matchers.MustMatchers
// My snippet probably which lives in another file
class MySnippet {
def showNames(xhtml : Group) : NodeSeq = {
val : User = User.currentUser.open_!
user.children.flatMap(child => bind("f", xhtml,
"name" => child.name
))
}
}
class MySnippetSpec extends FlatSpec with MustMatchers {
val session : LiftSession = new LiftSession("", StringHelpers.randomString(20), Empty)
override def withFixture(test: NoArgTest) {
// set up your db here
try {
// Initialize session state if it is not already
S.initIfUninitted(session) {
// Create and log-in the user
val user : User = User.create
user.firstName("XXX")
user.lastName("YYYY")
user.save
User.logUserIn(user)
test() // Invoke the test function
}
}
finally {
// tear down your db here
}
}
"My snippet" must "work like I want" in {
val xml = <xml:group><f:name>Name</f:name></xml:group>
val snippet = new MySnippet()
val output = snippet.showNames(xml)
// Do verification of data returned; assert if something is amiss
output must not be null // yes, this is valid ScalaTest matcher syntax
}
}Example code for Specs
With specs-1.6.1 (available here, user guide there), you can create a SpecContext to start the database before each example, create and log in a user then tear down the database. The code below also specifies that each example will be executed in the context of a lift session with the logged in user.
import com.foo.User
import net.liftweb.http.{S, LiftSession}
import net.liftweb.mapper.BaseMetaMapper
import net.liftweb.util._
import net.liftweb.common._
import org.specs._
import org.specs.specification._
class MySnippetSpec extends Specification with Contexts {
// run any block of code in a Lift session
val session = new LiftSession("", StringHelpers.randomString(20), Empty)
def inSession(a: =>Any) = {
S.initIfUninitted(session) { a }
}
// function to create and log-in a user
def loginUser = inSession {
val user: User = User.create
user.firstName("XXX")
user.lastName("YYYY")
user.save
User.logUserIn(user)
}
// specify what to do before/after each example
// specify that each example must run in the context of a session
new SpecContext {
beforeExample {
/* setup db here */
loginUser
}
afterExample { /* teardown db here */}
aroundExpectations(inSession(_))
}
"My snippet works with a user logged in" in {
val xml = <xml:group><f:name>Name</f:name></xml:group>
val snippet = new MySnippet()
val output = snippet.showNames(xml)
output must not be null
}
}Example code for Specs2
With specs2-1.8.2 (available here, user guide there), you can create a trait to start the database before each example, create and log in a user then tear down the database.
To launch specs2 tests on IntelliJ IDEA, you need at least version 11.1.
- Add Dependencies (documentation here)
Maven<dependency> <groupId>org.specs2</groupId> <artifactId>specs2_2.9.1</artifactId> <version>1.8.2</version> <scope>test</scope> </dependency>
SBT"org.specs2" %% "specs2" % "1.8.2" % "test"
- (optional) a WebSpec specs2 version with unit test is available on gist at
https://gist.github.com/2235088
- Some tear down traits (mutable Specification)
for example, a trait that initializes database
import org.specs2.mutable.{Specification}
import org.specs2.specification.{Fragments, Step}
import net.liftweb.common.{Full, Logger}
trait DatabaseSpec extends Specification {
def startDb = {
Logger.setup = Full(net.liftweb.util.LoggingAutoConfigurer())
Logger.setup.foreach { _.apply() }
import bootstrap.liftweb._
val boot= new Boot
boot.InitSchema // Or Some code to Initialize DB
}
def cleanDb = {}
/** the map method allows to "post-process" the fragments after their creation */
override def map(fs: =>Fragments) = Step(startDb) ^ fs ^ Step(cleanDb)
}
or a trait to setup Jetty Server in test mode
trait SetupAndDestroy extends Specification {
def setup(): Unit
def destroy(): Unit
/** the map method allows to "post-process" the fragments after their creation */
override def map(fs: =>Fragments) = Step(setup) ^ fs ^ Step(destroy)
}
trait JettySetupAndTearDown extends SetupAndDestroy { _: Specification =>
def setup() = JettyTestServer.start // See https://github.com/timperrett/lift-in-action/blob/master/chapter-14/src/test/scala/Servers.scala
def destroy() = JettyTestServer.stop()
}
Then some Specs:
class UserSpecs extends Specification with DatabaseSpec with JettySetupAndTearDown {
args(sequential=true) // This is important for using SessionVars, clear Db etc.
val testUrl = "http://foo.com/test/stateless"
// Create a new session for use in the tests
val testSession = MockWeb.testS(testUrl) {
S.session
}
/** the `User` context */
trait UserContext extends BeforeAfter {
// per test session
//val session : LiftSession = new LiftSession("", StringHelpers.randomString(20), Empty)
def inSession(a: =>Any) = {
// Initialize session state if it is not already
//S.initIfUninitted(session) { a }
S.initIfUninitted(testSession.get) { a }
}
def clearDb() = {
textFragment("Clear DB")
inSession(User.bulkDelete_!!())
inSession(UserAuth.bulkDelete_!!())
}
def before = clearDb()
def after = clearDb()
}
"user" should {
"be some stuff" in new UserContext {
val user = User.create
user.nickname.set("user1")
/* some code */
user.validate mustEqual Nil
user.save must_== true
}
}
}
Specs2 Specification and Mockito
an other example on how to use specs2 Specification and Mockito (not mutable) can be found at https://gist.github.com/1397007