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"
    
  • 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