views:

110

answers:

3

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)?

+1  A: 

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.

Ichorus
I could be wrong, but I think that since the Manifest is supplied by the compiler my technique would work regardless of how the trait is implemented. A technique based on runtime reflection, on the other hand, might run into trouble.
Aaron Novstrup
Right. I am guessing this is why all of this is not officially documented right now because the underlying implementation could change on you.
Ichorus
+1  A: 

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.

Thomas Jung
I was thinking that erasure would make it impossible to use reflection, but I guess not since the test class is not parameterized. Thanks!
Aaron Novstrup
A: 

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 ...
}
Aaron Novstrup