score:9

Accepted answer

There is limited support for dependency injection in specs2, mostly for execution environments or command-line arguments.

There is nothing preventing you from just using a lazy val and your favourite injection framework:

class MySpec extends Specification with Inject {
  lazy val reportService = inject[ReportService]

  ...
}

With Play and Guice, you could have a test helper such as this:

import play.api.inject.guice.GuiceApplicationBuilder
import scala.reflect.ClassTag    

trait Inject {
  lazy val injector = (new GuiceApplicationBuilder).injector()

  def inject[T : ClassTag]: T = injector.instanceOf[T]
}

score:2

My colleague suggested a "low-tech" workaround. In the test, instantiate service classes with new:

class ReportServiceSpec extends Specification {
  val service = new ReportService(new UserService, new SupportService)
  // ...
}

This also works:

class ReportServiceSpec @Inject()(userService: UserService) extends Specification {
  val service = new ReportService(userService, new SupportService) 
  // ...    
}

Feel free to post more elegant solutions. I've yet to see a simple DI solution that works (with Guice, Play's default).

Does anyone else find it curious that Play's default test framework does not play well with Play's default DI mechanism?


Edit: In the end I went with an "Injector" test helper, almost the same as what Eric suggested:

Injector:

package testhelpers

import play.api.inject.guice.GuiceApplicationBuilder    
import scala.reflect.ClassTag

/**
 * Provides dependency injection for test classes.
 */
object Injector {
  lazy val injector = (new GuiceApplicationBuilder).injector()

  def inject[T: ClassTag]: T = injector.instanceOf[T]
}

Test:

class ReportServiceSpec extends Specification {
  val service = Injector.inject[ReportService]
  // ...
}

score:3

If you really need runtime dependency injection, then it's better to use Guice loading, I guess:

package services

import org.specs2.mutable.Specification

import scala.reflect.ClassTag
import com.google.inject.Guice

// Something you'd like to share between your tests
// or maybe not
object Inject {
  lazy val injector = Guice.createInjector()

  def apply[T <: AnyRef](implicit m: ClassTag[T]): T = 
    injector.getInstance(m.runtimeClass).asInstanceOf[T]
}

class ReportServiceSpec  extends Specification {
  lazy val reportService: ReportService = Inject[ReportService]

  "ReportService" should {
    "Work" in {
      reportService.foo mustEqual 2
    }
  }
}

Alternatively you can implement Inject object as

import scala.reflect.ClassTag
import play.api.inject.guice.GuiceApplicationBuilder  

object Inject {
  lazy val injector = (new GuiceApplicationBuilder).injector()
  def apply[T : ClassTag]: T = injector.instanceOf[T]
}

It depends whether you want to use Guice directly, or thru play wrappers.


Looks like you are out of luck ATM: The comment says

Try to create an instance of a given class by using whatever constructor is available and trying to instantiate the first parameter recursively if there is a parameter for that constructor.

val constructors = klass.getDeclaredConstructors.toList.filter(_.getParameterTypes.size <= 1).sortBy(_.getParameterTypes.size)

i.e. Specs2 doesn't provide own DI out-of-the box,


Or you can reimplement the functionality yourself, if Guice isn't working for you.

App code:

package services

import javax.inject.Inject

class ReportService @Inject()(userService: UserService, supportService: SupportService) {
  val foo: Int = userService.foo + supportService.foo
}

class UserService  {  
   val foo: Int = 1
}
class SupportService {  
    val foo: Int = 41
}

Test code

package services

import org.specs2.mutable.Specification

import scala.reflect.ClassTag
import java.lang.reflect.Constructor

class Trick {
  val m: ClassTag[ReportService] = implicitly
  val classLoader: ClassLoader = m.runtimeClass.getClassLoader

  val trick: ReportService = Trick.createInstance[ReportService](m.runtimeClass, classLoader)
}

object Trick {
  def createInstance[T <: AnyRef](klass: Class[_], loader: ClassLoader)(implicit m: ClassTag[T]): T = {
    val constructors = klass.getDeclaredConstructors.toList.sortBy(_.getParameterTypes.size)
    val constructor = constructors.head

    createInstanceForConstructor(klass, constructor, loader)
  }

  private def createInstanceForConstructor[T <: AnyRef : ClassTag]
    (c: Class[_], constructor: Constructor[_], loader: ClassLoader): T = {
    constructor.setAccessible(true)

    // This can be implemented generically, but I don't remember how to deal with variadic functions
    // generically. IIRC even more reflection.
    if (constructor.getParameterTypes.isEmpty)
      constructor.newInstance().asInstanceOf[T]

    else if (constructor.getParameterTypes.size == 1) {
      // not implemented
      null.asInstanceOf[T]
    } else if (constructor.getParameterTypes.size == 2) {
      val types = constructor.getParameterTypes.toSeq
      val param1 = createInstance(types(0), loader)
      val param2 = createInstance(types(1), loader)
      constructor.newInstance(param1, param2).asInstanceOf[T]
    } else {
      // not implemented
      null.asInstanceOf[T]
    }
  }
}

// NB: no need to @Inject here. The specs2 framework does it for us.
// It sees spec with parameter, and loads it for us.
class ReportServiceSpec (trick: Trick) extends Specification {
  "ReportService" should {
    "Work" in {
      trick.trick.foo mustEqual 2
    }
  }
}

And that expectedly fails with

[info] ReportService should
[error]   x Work
[error]    '42' is not equal to '2' (FooSpec.scala:46)

If you don't need runtime dependency injection, then it's better to use cake pattern, and forget reflection all-together.


Related Query

More Query from same tag