In the previous post, I explained how to use Guice with Play! 2.1 for easy dependency injection. At the end, I demonstrated how to test Play! controllers directly, without creating a FakeApplication
. This is great for testing actual controller functionality, without the overhead of loading a new Play! application. It also works very well with Play 2.1′s constructible controllers, allowing you to explicitly pass in class instances that you use.
The performance boost is quite significant. I actually didn’t mention how much direct controller tests speed up your unit tests, so I created a simple benchmark using the Play! 2.1 Guice Example application.
I ran the 3 tests from that project (normal Play! tests, explicitly injected controller, and direct controller instance) 10,000 times, and took the median time (measured in nanoseconds, displayed here in milliseconds).
def timer(f: => Any): Long = { val startTime = System.nanoTime val ret = f val endTime = System.nanoTime endTime - startTime } def iterTest(f: => Any): Double = { (for(i <- 0 to 10000) yield timer(f)).sorted.apply(5000) } val norm_time = iterTest { running(FakeApplication()) { val translated = route(FakeRequest(GET, "/greet/Barney")).get contentAsString(translated) must contain ("Barney") } } println(s"Normal Play! way:\t$norm_time") val inj_time = iterTest { running(FakeApplication(withGlobal = Some(FakeTranslatorGlobal))) { val home = route(FakeRequest(GET, "/greet/Barney")).get contentAsString(home) must contain ("Hello Barney") } } println(s"Injected:\t\t$inj_time") val direct_time = iterTest { val controller = new Translate(new FakeTranslator) val result = controller.greet(name = "Barney")(FakeRequest()) contentAsString(result) must contain ("Hello Barney") } println(s"Direct:\t\t\t$direct_time") |
The results? Direct controller tests performed two orders of magnitude faster than standard Play! tests. Normal Play! controller testing took 5.52ms, the explicitly injected controller took 4.51ms, and the direct controller call took 0.09ms.
This result is intuitive (much less overhead), but many large Play! applications fail to test this way. For a well-tested codebase with thousands of tests, this can shave several minutes off each deployment. In conjunction with dependency injection, this removes code and time bottlenecks to having a very well tested codebase.