Reputation: 21017
I'm writing a ServletUnitTest trait in Scala to provide a convenience API for ServletUnit. I have something like the following in mind:
/**
* Utility trait for HttpUnit/ServletUnit tests
*
* @param [T] Type parameter for the class under test
*/
trait ServletUnitTest[T <: HttpServlet] {
/**
* Resource name of the servlet, used to construct the servlet URL.
*/
val servletName: String
/**
* Servlet class under test
*/
implicit val servletClass: Manifest[T]
/**
* ServletUnit {@link ServletRunner}
*/
sealed lazy val servletRunner: ServletRunner = {
val sr = new ServletRunner();
sr.registerServlet(servletName, servletClass.erasure.getName);
sr
}
/**
* A {@link com.meterware.servletunit.ServletUnitClient}
*/
sealed lazy val servletClient = servletRunner.newClient
/**
* The servlet URL, useful for constructing WebRequests
*/
sealed lazy val servletUrl = "http://localhost/" + servletName
def servlet(ic: InvocationContext) = ic.getServlet.asInstanceOf[T]
}
class MyServletTest extends ServletIUnitTest[MyServlet] {
val servletName = "download"
// ... test code ...
}
This code doesn't compile as written, but hopefully my intent is clear. Is there a way to do this (with or without Manifests)?
Upvotes: 1
Views: 412
Reputation: 208
While researching this topic, I found about a solution in this scala-list post by Jorge Ortiz, which did the trick for me, and is simpler than Aaron's. In essence, his solution is (paraphrasing):
trait A[T] {
implicit val t: Manifest[T]
}
class B[T: Manifest] extends A[T] {
override val t = manifest[T]
}
(I'm ignoring the OP request to be 2.7.7 compatible as I'm writing this in 2011...)
Upvotes: 2
Reputation: 21017
I found a solution that works, but it's pretty awkward since it requires the test class to call a method (clazz) on the trait before any of the trait's lazy vals are evaluated.
/**
* Utility trait for HttpUnit/ServletUnit tests
*
* @param [T] Type parameter for the class under test
*/
trait ServletUnitTest[T <: HttpServlet] {
/**
* Resource name of the servlet, used to construct the servlet URL.
*/
val servletName: String
/**
* Servlet class under test
*/
val servletClass: Class[_] // = clazz
protected def clazz(implicit m: Manifest[T]) = m.erasure
/**
* ServletUnit {@link ServletRunner}
*/
sealed lazy val servletRunner: ServletRunner = {
val sr = new ServletRunner();
sr.registerServlet(servletName, servletClass.getName);
sr
}
/**
* A {@link com.meterware.servletunit.ServletUnitClient}
*/
sealed lazy val servletClient = servletRunner.newClient
/**
* The servlet URL, useful for constructing WebRequests
*/
sealed lazy val servletUrl = "http://localhost/" + servletName
def servlet(ic: InvocationContext) = ic.getServlet.asInstanceOf[T]
}
class MyServletTest extends ServletIUnitTest[MyServlet] {
val servletName = "download"
val servletClass = clazz
// ... test code ...
}
Upvotes: 0
Reputation: 33092
The type information is accessible with the Java reflection API. It's not pretty but it works:
trait A[T]{
def typeParameter = {
val genericType = getClass.getGenericInterfaces()(0).asInstanceOf[ParameterizedType]
genericType.getActualTypeArguments()(0)
}
}
class B extends A[Int]
new B().typeParameter -> java.lang.Integer
Some invariant checks should be added I've only implemented the happy path.
Upvotes: 1
Reputation: 4617
For now, Scala represents traits as interfaces so this technique will work. There are some problems with this approach to implementing traits, however, in that when methods are added to a trait, the implementing class will not necessarily recompile because the interface representation only has a forwarding method pointing to another class that actually implements the method concretely. In response to this there was talk earlier this year of using interface injection into the JVM at runtime to get around this problem. If the powers that be use this approach then the trait's type information will be lost before you can capture it.
Upvotes: 1