Using Google Guice in specs2 contexts

199 views
Skip to first unread message

etorreborre

unread,
Aug 4, 2011, 6:07:06 PM8/4/11
to specs2...@googlegroups.com
[This is the continuation of a thread started on the specs-users mailing list]

Hi Dirk, 

The following works if you use the Before trait, instead of calling the Injector directly in the body of the context trait:

package examples
import org.specs2.mutable._
import org.specs2.specification.Scope
import com.google.inject.{Inject, Module, Guice, Binder}

class TestSpec extends Specification {
  "this is an example" in new context {
    emailService must not be(null)
  }
  trait context extends GuiceContext {
    def module = new MyModule
    @Inject var emailService : EmailService = null
  }
}
class EmailService

class MyModule extends Module {
  def configure(binder: Binder) {  
    binder.bind(classOf[EmailService]).toInstance(new EmailService);  
  }
}

trait GuiceContext extends Before {
  def module: Module
  def before = Guice.createInjector(module).injectMembers(this)
}

Eric.

Dirk Louwers

unread,
Aug 4, 2011, 6:46:58 PM8/4/11
to specs2...@googlegroups.com
Aha, so basically creating a context trait instead of a class will do the trick?

Am I right that the following is a good combination of context traits where order is an issue like here?:

trait GuiceAndMongoDbContext extends MongoDbContext with GuiceContext {
  override def before {
    super[GuiceContext].before
    super[MongoDbContext].before
  }
}

Best,

Dirk Louwers

etorreborre

unread,
Aug 4, 2011, 6:47:08 PM8/4/11
to specs2...@googlegroups.com
Here is a more complete answer with an After trait to clean-up the database after each example (as requested here):

package examples
import org.specs2.mutable._
import org.specs2.specification.Scope
import com.google.inject.{Inject, Module, Guice, Binder}

class TestSpec extends Specification {
  "this is an example" in new context {
    println("executing example")
    emailService must not be(null)
  }
  trait context extends MongoDbContext with GuiceContext with BeforeAfter {
    def module = new MyModule
    @Inject var emailService : EmailService = null
  }
}
class EmailService

class MyModule extends Module {
  def configure(binder: Binder) {  
    binder.bind(classOf[EmailService]).toInstance(new EmailService);  
  }
}

class MongoConfig {
  def init = { println("initialized the config to drop the database"); this }
  def dropDatabase = println("dropped database")
}
trait MongoDbContext extends After {
  @Inject
  var mongoConf: MongoConfig = null
  
  def mongoConfig = mongoConf.init

  def after = mongoConfig.dropDatabase
}

trait GuiceContext extends Before {
  def module: Module
  def before = Guice.createInjector(module).injectMembers(this)
}

trait BeforeAfter extends org.specs2.specification.BeforeAfter with DelayedInit {
  override def delayedInit(x: => Unit): Unit = try { before; x } finally { after }
}

Note: the org.specs2.mutable.BeforeAfter trait does not exist at the moment but I'm adding it to the next 1.6-SNAPSHOT which should be published in the next hour.

Eric.

etorreborre

unread,
Aug 4, 2011, 7:00:30 PM8/4/11
to specs2...@googlegroups.com
Aha, so basically creating a context trait instead of a class will do the trick?

I think that doing the injection in the trait body might be the issue as well.

> trait GuiceAndMongoDbContext extends MongoDbContext with GuiceContext {

That looks right to me but of course nothing beats a real test!

Eric.

Dirk Louwers

unread,
Aug 6, 2011, 10:18:22 AM8/6/11
to specs2...@googlegroups.com
Hi,

Thanks for the great help so far!

I have not been able to combine traits like you suggested:


trait context extends MongoDbContext with GuiceContext with BeforeAfter {
  def module = new MyModule
  @Inject var emailService : EmailService = null
}

This caused NPE's again. I have tried overriding the before and after methods and calling the respective super's in order. This caused the after method to not be called anymore:

trait GuiceAndMongoDbContext extends MongoDbContext with GuiceContext with BeforeAfter {


  override def before {
    super[GuiceContext].before
    super[MongoDbContext].before
  }

  override def after {
    super[MongoDbContext].after
  }
}

The only way I could get it to work was defining the GuiceAndMongoDbContext from the ground up like:

trait GuiceAndMongoDbContext extends BeforeAfter {

  implicit def module2moduleList(mod: AbstractModule): List[AbstractModule] = {
    mod :: Nil
  }

  def module: List[AbstractModule]
  def mongoConfig: MongoConfig

  def before {
    Guice.createInjector(module).injectMembers(this)
    mongoConfig.init
  }

  def after {
    MongoDB.use(mongoConfig.id) { db =>
      db.dropDatabase()
    }
  }
}

This is far from elegant since it doesn't reuse code and makes it all less maintainable. Would love to hear what your take is on this usage scenario.

Best,

Dirk Louwers



etorreborre

unread,
Aug 6, 2011, 9:23:41 PM8/6/11
to specs2...@googlegroups.com
There's another to combine context traits while controlling the initialization order:

// this can be an object
object MongoDbContext extends MongoDbContext

// we define the GuiceContext as a BeforeAfter trait to be able to compose it later on
trait GuiceContext extends BeforeAfter {
  def module: Module
  lazy val injector = Guice.createInjector(module)

  // a seq of targets must be defined
  def targets: Seq[AnyRef]
  def before = {
    println("doing the guice injection")
    targets foreach injector.injectMembers
  }
  def after = ()
}

// the createContext method creates a GuiceContext with a seq of objects to inject
object GuiceContext {
  def createContext(toInject: AnyRef*): GuiceContext = new GuiceContext { 
    def module = new MyModule
    def targets: Seq[AnyRef] = toInject
  }
}

// the context to use in our examples
trait context extends BeforeAfter { 

  @Inject var emailService : EmailService = null

  // we first do the injection on 'this' and on the MongoDbContext, then we apply the
  // MongoDbContext to do the mongo init
  lazy val composed = MongoDbContext compose GuiceContext.createContext(this, MongoDbContext)
// some boilerplate code to delegate to the composed MongoDbContext
  def before = composed.before
  def after  = composed.after
}

In this approach there is a bit of wiring to be done but the MongoDbContext and the GuiceContext traits can be defined independently of each other.

Eric.
Reply all
Reply to author
Forward
0 new messages