Contexts as Traits

33 views
Skip to first unread message

Dirk Louwers

unread,
Jul 29, 2011, 12:18:43 PM7/29/11
to specs...@googlegroups.com
Hi,

I have been trying the following:

* Write a trait like trait MyXSupport { this: Specification => }
* In these traits I create and apply a SpecContext like:
val myCtx = new SpecContext( ... )
myCtx(this)
* Combine these like trait MyCombinedSupport extends MyXSupport with MyYSupport
* Extend this in a Specification subclass like MySpec extends Specification with MyCombinedSupport

Unfortunately it seems like only one context ever gets invoked and the other is ignored. I have seen a post on these groups wondering about combining contexts but the proposed solution doesn't seem to work.

I am using Specs 1.6.8 on Scala 2.9.0-1

Any idea how to make this work or any other suggestion how to best reuse and combine SpecContext in several Specifications?

Best,

Dirk Louwers

etorreborre

unread,
Aug 1, 2011, 9:58:23 PM8/1/11
to specs...@googlegroups.com
Hi Dirk,

Sorry for the late reply. I don't think there's a direct way to do that with the current state of specs. 

I would suggest either:

 - to assemble different setup functions coming from different traits into one SpecContext
 - switch to specs2 where you'd have more flexibility on that front (I can assist you with that if you meet any issue)

Eric.

Dirk Louwers

unread,
Aug 4, 2011, 4:03:43 PM8/4/11
to specs...@googlegroups.com
Hi Eric,

I followed your advise and upgraded to Specs2. After rewriting a bunch of matcher it was time to take on the contexts. Eventually I did manage to get things running with the original Specs but decided to try Specs2 and see if the solutions for context would be more elegant. Unfortunately I have not been able to get things running again in Specs2.

Let me first create some context:
Many of my tests require Google Guice to inject them. In the past I have been able to inject the Specification by implementing a GuiceSupport trait that needed the subclass to implement a function providing a Guide Module and a reference to the Spec ('this' from the trait doesn't cut it apparently). The trait would inject the passed reference to the Spec in a 'before' context. This worked but was far from elegant.

Now I have tried something similar with the following:

trait GuiceContext extends Scope {

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

  def module: List[AbstractModule]
  def target: AnyRef

  Guice.createInjector(module).injectMembers(target)
}

And then in the Spec define the following inner class:

class Context extends GuiceContext {
  @Inject val emailDao: EmailDao = null
  def module = TestModule
  def target = this
}

And then run the example like:

"recognise gmail.com as a valid domain" in new Context { ... }

And unfortunately NPEs are the result. The inject code gets called but the 'target' type now turns out to be some odd anonymous class that Guice cannot find annotations on. I thought maybe the fact that it's an inner class has thrown Guice off so I declared as an outer class. Same problems.

I finally did manage to get things working by declaring the injectable val as a member of the Spec and passing it as the target in the context like so:

class BasicEmailDaoSpec extends Specification {

  @Inject val emailDao: EmailDao = null

  "The BasicEmailDao" should {
    "recognise gmail.com as a valid domain" in new Context {
      emailDao.checkValidity("gmail.com") must beTrue
    }
    "reject gnokpokdffr.kwaka as an invalid domain" in new Context {
      emailDao.checkValidity("gnokpokdffr.kwaka") must beFalse
    }
  }

  class Context extends GuiceContext {
    def module = TestModule
    val target = this
  }
}

I cannot help but wonder if there are any side effects to using this val declared out of context. Hope you can shed some light on this issue.

Best,

Dirk Louwers

Dirk Louwers

unread,
Aug 4, 2011, 5:28:25 PM8/4/11
to specs...@googlegroups.com
(...continued from last...)

The next contexts to rewrite were my MongoDbContext and the combined GuiceAndMongoDbContext. The MongoDB context basically initializes a connection from a random database name provider and afterwards drops the whole database. This is needed because MongoDB has no declarative transaction support and I like to keep the data isolated inside my integration tests.

This class looks like:

trait MongoDbContext extends After {

  def mongoConfig: MongoConfig

  mongoConfig.init

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

And the combination of both like:

trait GuiceAndMongoDbContext extends MongoDbContext with GuiceContext

Used like:

class Context extends GuiceAndMongoDbContext {
  def module = TestModule
  def target = MongoSpec.this
  def mongoConfig = null // Help! Where do I get my value from!
}

Unfortunately, as you can see, the GuiceAndMongoDbContext counts on the mongoConfig being injected in the context and as I've illustrated in my last post this is not possible AFAIK due to Guice's limitations. To work around this I came up with an even more inelegant solution, using ContextHolders:

object ContextHolder {
   @Inject val mongoConfig: MongoConfig = null
}

class Context extends GuiceAndMongoDbContext {
  def module = TestModule
  def target = ContextHolder
  def mongoConfig = ContextHolder.mongoConfig
}

However, now the examples take their context from outside the formal context, namely the ContextHolder. I can imagine this having yet more side-effects, and would like to know if this is safe. If anyone has come across a similar scenario and has come up with a fantastic solution I'd love to know. If this turns out to be safe after all I hope people can use this information.

Best,

Dirk Louwers

Dirk Louwers

unread,
Aug 4, 2011, 5:51:08 PM8/4/11
to specs...@googlegroups.com
(...continued from last...)

Ok, turns out that this solution working is non-deterministic. It worked a few times but threw NPEs many times later. Will have to come up with another workaround.

Best,

Dirk Louwers

etorreborre

unread,
Aug 4, 2011, 6:08:39 PM8/4/11
to specs...@googlegroups.com
Hi Dirk,

You can avoid having variables shared by the whole specification by using a Before trait instead of Scope.

I provided the answer on the specs2-users mailing list because I think that it is more relevant there: https://groups.google.com/d/topic/specs2-users/ZgwKzMzuJe8/discussion

Eric.
Reply all
Reply to author
Forward
0 new messages