Error with Play 2.4 Tests : The CacheManager has been shut down. It can no longer be used
Asked Answered
T

3

9

Our application is built on Play 2.4 with Scala 2.11 and Akka. Database used is MySQL.

Cache is used heavily in our application.We use Play's default EhCache for caching.

Our sample code snippet :

import play.api.Play.current
import play.api.cache.Cache

case class Sample(var id: Option[String],
                 //.. other fields
)

class SampleTable(tag: Tag)
  extends Table[Sample](tag, "SAMPLE") {
  def id = column[Option[String]]("id", O.PrimaryKey)
  // .. other field defs
}

object SampleDAO extends TableQuery(new SampleTable(_)) with SQLWrapper {
  def get(id: String) : Future[Sample] = {
    val cacheKey = // our code to generate a unique cache key
    Cache.getOrElse[Future[[Sample]](cacheKey) {
      db.run(this.filter(_.id === id).result.headOption)
    }
  }
}

We use Play's inbuilt Specs2 for testing.

var id = "6879a389-aa3c-4074-9929-cca324c7a01f"

  "Sample Application " should {
    "Get a Sample" in new WithApplication {
      val req = FakeRequest(GET, s"/v1/samples/$id")
      val result = route(req).get
      assertEquals(OK, status(result))
      id = (contentAsJson(result).\("id")).get.toString().replaceAllLiterally("\"", "")
   }

But While unit-testing we often encounter the below error.

[error]    1) Error in custom provider, java.lang.IllegalStateException: The  CacheManager has been shut down. It can no longer b
e used.
[error]      at play.api.cache.EhCacheModule.play$api$cache$EhCacheModule$$bindCache$1(Cache.scala:181):
[error]    Binding(interface net.sf.ehcache.Ehcache qualified with QualifierInstance(@play.cache.NamedCache(value=play)) to Prov
iderTarget(play.api.cache.NamedEhCacheProvider@7c8b0968)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.ap
i.inject.guice.GuiceableModuleConversions$$anon$1)
[error]      while locating net.sf.ehcache.Ehcache annotated with @play.cache.NamedCache(value=play)
[error]      at play.api.cache.EhCacheModule.play$api$cache$EhCacheModule$$bindCache$1(Cache.scala:182):
[error]    Binding(interface play.api.cache.CacheApi qualified with QualifierInstance(@play.cache.NamedCache(value=play)) to Pro
viderTarget(play.api.cache.NamedCacheApiProvider@38514c74)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.
api.inject.guice.GuiceableModuleConversions$$anon$1)
[error]      while locating play.api.cache.CacheApi annotated with @play.cache.NamedCache(value=play)
[error]      while locating play.api.cache.CacheApi
[error]
[error]    1 error (InjectorImpl.java:1025)
[error] com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1025)
[error] com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[error] play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:321)
[error] play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:316)
[error] play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[error] play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[error] play.utils.InlineCache.fresh(InlineCache.scala:69)
[error] play.utils.InlineCache.apply(InlineCache.scala:62)
[error] play.api.cache.Cache$.cacheApi(Cache.scala:63)
[error] play.api.cache.Cache$.getOrElse(Cache.scala:106

We look forward for help on either resolving the above issue or ways to implement a mock cache exclusively for Testing.

Thanks in Advance.

Tishtisha answered 25/6, 2015 at 5:25 Comment(2)
This issue is usually due to having two Play applications somehow running at the same time (which due to EHCache's global singleton nature causes problems, since when one application shuts down the other will still be using it.) Using WithApplication should start up and shut down a fresh app for each spec, but I've found that certain plugins (or rather, modules now) can cause issues if they affect the app lifecycle. You don't provide enough info to fully diagnose, but try to find out how two (fake)applications could be running simultaneously.Descender
Yes Mikesname .. even I have the same dbt that when every test spec runs as a single new application .. the cache should automatically start up and shut down for every spec . But as you mentioned the singleton nature of the cache object in the ehcachemodule might be the issue .. Any how I started using Guice DI to inject cache instance at run time .. and implemented a separate Fake Cache as suggested by @Steve below .. I disabled default EhCacheModule and enabled this Fake cache module for test environment .. And my tests are now running fine :)Tishtisha
S
5

I had a similar issue, so I stubbed out a cache implementation and swapped it in for the tests.

class FakeCache extends CacheApi {
  override def set(key: String, value: Any, expiration: Duration): Unit = {}

  override def get[T](key: String)(implicit evidence$2: ClassManifest[T]): Option[T] = None

  override def getOrElse[A](key: String, expiration: Duration)(orElse: => A)(implicit evidence$1: ClassManifest[A]): A = orElse

  override def remove(key: String): Unit = {}
}

Override the injection:

class AbstractViewTest extends PlaySpecification {

  def testApp(handler: DeadboltHandler): Application = new GuiceApplicationBuilder()
                                                   .overrides(bind[CacheApi].to[FakeCache])
                                                   .in(Mode.Test)
                                                   .build()

}

You can see how I use this on GitHub: https://github.com/schaloner/deadbolt-2-scala/blob/master/code/test/be/objectify/deadbolt/scala/views/AbstractViewTest.scala

Sternway answered 26/6, 2015 at 10:25 Comment(2)
Hi @Steve... Thnq for the reply.. While trying with the above solution provided by you , I was struck with the following issue . Can you help me with this issue : #31101034Tishtisha
@bhavya sorry, missed this. I agree with the answer given - when you're using DI, objects can be converted to programmtic singleton classes.Sternway
F
3

Another solution would be to call the sequential method on the beginning of each test.

class MySpec extends Specification {
  sequential

  ...
}

Note

parallelExecution in Test := false

Should be set in the build.sbt file too.

Fite answered 24/8, 2015 at 10:51 Comment(0)
S
1

I think bypassing cache testing by faking a cache is a bad practice. It invalidates your tests, all because you are trying to not use EhCache in a scalable and distributed way. A much better approach is to implement a @Singleton interface as detailed here: https://mcmap.net/q/1316782/-how-to-use-the-play-cache-cacheapi-in-a-static-method-in-play-framework-2-4-2

Thanks to https://stackoverflow.com/users/2145368/mon-calamari

Shiller answered 1/1, 2016 at 19:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.