score:9
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.
Source: stackoverflow.com
Related Query
- Specs2: how to test a class with more than one injected dependency?
- How do I define Json format for a case class with more than one apply method?
- How to filter a list with a condition that depends on more than one element
- How to initialize a field with more than one operation?
- How to deal with more than one categorical feature in a decision tree?
- how to map form having more attributes than case class in play 2 with scala
- How can one "associate" test scripts with a Class in Scala
- How to test Akka Actor functionality by mocking one or more methods in it
- Pattern matching with more than one match
- Scala, how to read more than one integer in one line in and get them in one variable each?
- Play json merge formats for case class with more than 22 fields
- Scala: how to split using more than one delimiter
- How can I add jars from more than one unmanaged directory in an SBT .scala project configuration
- How to create schema (StructType) with one or more StructTypes?
- Map table with more than 22 columns to Scala case class by Slick 2.1.0
- Scala style: More than one class in a file?
- In Scala, how does one write a class with a constructor, not all of whose arguments are class members?
- How to test actors with components injected by Guice in Play! scala 2.5
- Functor implementation for types with more than one type
- How to write a symmetric Play Json formatter for a case class with one field in scala?
- How to write implicit Writes for case class having more than 22 fields
- Can we call method having more than one arguments with list.map?
- Parsing options that take more than one value with scopt in scala
- presentation compiler: type completion in method call with more than one argument
- How String class taken high priority than AnyRef while calling with null value?
- How to add more than one component to MainFrame contents
- How to extends a class that received parameters with Guice dependency injection
- With ScalaCheck forAll, how do I set one parameter of case class and let the rest be arbitrarily generated?
- How to use Scala/Akka Http to process more than one HTTP headers
- Spark - after a withColumn("newCol", collect_list(...)) select rows with more than one element
More Query from same tag
- Folders/packages in sbt ./project folder
- PlayFramework 2.6 shutdown hook
- Akka - how to maintain state through a context switch
- How are Scala's local lazy variables implemented?
- Scala Sealed trait def to val (How to set value?)
- Finding correct jar file to work with scala example
- Verifying the existence of a number in a Play Scala form
- Scala: Main method not found in class
- Update variables using multiple values returned from a function in Scala
- why method needs 'abstract override' modifier
- Scala underscores in names
- How to find the GPS coordinates (latitude, longitude) of the foci of an ellipse?
- print all emoji characters with loop
- Mix several Enumerations into one
- Store about 120 Column based flat data structure to Spark Parquet Scala
- Scalding job failing with VerifyError on EMR version 4.2.0
- Jsoup select not returning all elements
- Calling Scala Method from Java - Set<Object> or Set<Long>?
- Implementing `sequence` on a Monad
- How to add List of Option of doubles in scala
- Unable to print line or reach break point in Gatling 3.0 simulation
- Unable to import jar in scala console to run spark programe
- Combining JavaFX and Scala - is it possible?
- Play Framework 2.2.2 with JDK 1.7.0_79 Starting issue
- Initialize a list from a function return in Scala
- Error:scalac: 'jvm-1.9' is not a valid choice for '-target'
- Spark: Using named arguments to submit application
- Akka and simple local cache (possibly Guava)
- Integrating native system libraries with SBT
- What is scala's version of ArrayList and Tuple?