Test errors after refactoring React-redux example


#1

Hi,

I refactored the react redux example into one project and removed some stuff that I didn’t need. I’m running into some issues running the default and I can’t quite figuring out what is going on. It seems like it is something to do with the Quartz scheduler instantiating multiple times. I’m pretty new to Play! and specs2 so this error is cryptic to me. This error is the result of a simplified UserControllerSpec

class UserControllerSpec extends ApiSpecification with AuthSpecification {
  sequential

  "The `user` action" should {
    "return HTTP status 401 if the user isn't authenticated" in new Context {
      new WithApplication(application) {
        val request = FakeRequest()

        Response(
          UNAUTHORIZED,
          controller.get(request),
          "auth.unauthorized",
          Messages("auth.unauthorized")
        )
      }
    }

//    "return HTTP status 200 with the user" in new Context {
//      new WithApplication(application) {
//        val request = FakeRequest().withAuthenticator(loginInfo)
//
//        Response(
//          OK,
//          controller.get(request),
//          "auth.user.successful",
//          Messages("request.ok"),
//          user
//        )
//      }
//    }
  }

  /**
    * The context.
    */
  trait Context extends ApiContext[UserController] with AuthContext
}

And the logs:

$ testOnly controllers.UserControllerSpec
[info] a.e.s.Slf4jLogger - Slf4jLogger started
[info] swagger - Swagger - starting initialisation...
[info] swagger - ControllerScanner - looking for controllers with API annotation
[info] swagger - Swagger - initialization done.
[info] o.q.c.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
[info] o.q.c.QuartzScheduler - Quartz Scheduler v.2.2.3 created.
[info] o.q.s.RAMJobStore - RAMJobStore initialized.
[info] o.q.c.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'QuartzScheduler~application' with instanceId 'application'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

[info] o.q.i.DirectSchedulerFactory - Quartz scheduler 'QuartzScheduler~application
[info] o.q.i.DirectSchedulerFactory - Quartz scheduler version: 2.2.3
[info] o.q.c.QuartzScheduler - Scheduler QuartzScheduler~application_$_application started.
[info] m.QuartzSchedulerProvider - Add stop hock for QuartzScheduler: QuartzScheduler~application
[info] c.t.a.e.q.QuartzSchedulerExtension - Setting up scheduled job 'AuthTokenCleaner', with 'com.typesafe.akka.extension.quartz.QuartzCronSchedule@7914e88a'
[info] r.a.MongoDriver - No mongo-async-driver configuration found
[info] r.a.MongoDriver - [Supervisor-1] Creating connection: Connection-2
[info] a.e.s.Slf4jLogger - Slf4jLogger started
[info] r.c.a.MongoDBSystem - [Supervisor-1/Connection-2] Starting the MongoDBSystem akka://reactivemongo/user/Connection-2
[info] swagger - Swagger - starting initialisation...
[info] swagger - Swagger - initialization done.
[info] o.q.c.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
[info] o.q.c.QuartzScheduler - Quartz Scheduler v.2.2.3 created.
[info] o.q.s.RAMJobStore - RAMJobStore initialized.
[info] o.q.c.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'QuartzScheduler~application' with instanceId 'application'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

[info] o.q.i.DirectSchedulerFactory - Quartz scheduler 'QuartzScheduler~application
[info] o.q.i.DirectSchedulerFactory - Quartz scheduler version: 2.2.3
[info] p.m.r.DefaultReactiveMongoApi - ReactiveMongoApi stopping...
[info] p.m.r.DefaultReactiveMongoApi - ReactiveMongoApi connections are stopped
[info] r.a.MongoDriver - [Supervisor-1] Closing instance of ReactiveMongo driver
[info] r.c.a.MongoDBSystem - [Supervisor-1/Connection-2] Stopping the MongoDBSystem akka://reactivemongo/user/Connection-2
[INFO] [04/13/2018 09:37:15.072] [reactivemongo-akka.actor.default-dispatcher-2] [akka://reactivemongo/user/Monitor-Connection-2] Message [reactivemongo.core.actors.Close$] without sender to Actor[akka://reactivemongo/user/Monitor-Connection-2#-728206706] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
[info] r.a.MongoDriver - [Supervisor-1] Stopping the monitor...
[info] m.QuartzSchedulerProvider - Shutdown QuartzScheduler: QuartzScheduler~application
[info] o.q.c.QuartzScheduler - Scheduler QuartzScheduler~application_$_application shutting down.
[info] o.q.c.QuartzScheduler - Scheduler QuartzScheduler~application_$_application paused.
[info] o.q.c.QuartzScheduler - Scheduler QuartzScheduler~application_$_application shutdown complete.
[info] swagger - Swagger - stopped.
[error] j.AuthTokenCleaner - 
=================================
Start to cleanup auth tokens
=================================
Couldn't cleanup auth tokens because of unexpected error
=================================

java.lang.IllegalStateException: cannot enqueue after timer shutdown
	at akka.actor.LightArrayRevolverScheduler.scheduleOnce(LightArrayRevolverScheduler.scala:136)
	at akka.actor.Scheduler.scheduleOnce(Scheduler.scala:140)
	at akka.actor.Scheduler.scheduleOnce$(Scheduler.scala:137)
	at akka.actor.LightArrayRevolverScheduler.scheduleOnce(LightArrayRevolverScheduler.scala:37)
	at akka.pattern.FutureTimeoutSupport.after(FutureTimeoutSupport.scala:25)
	at akka.pattern.FutureTimeoutSupport.after$(FutureTimeoutSupport.scala:20)
	at akka.pattern.package$.after(package.scala:41)
	at reactivemongo.api.MongoConnection.doRetry$1(MongoConnection.scala:180)
	at reactivemongo.api.MongoConnection.wait$1(MongoConnection.scala:191)
	at reactivemongo.api.MongoConnection.$anonfun$waitIsAvailable$5(MongoConnection.scala:182)
[info] UserControllerSpec
[info] The `user` action should
[error]   ! return HTTP status 401 if the user isn't authenticated
[error]    org.quartz.SchedulerException: Scheduler with name 'QuartzScheduler~application' already exists. (SchedulerRepository.java:80)
[error] org.quartz.impl.SchedulerRepository.bind(SchedulerRepository.java:80)
[error] org.quartz.impl.DirectSchedulerFactory.createScheduler(DirectSchedulerFactory.java:529)
[error] org.quartz.impl.DirectSchedulerFactory.createScheduler(DirectSchedulerFactory.java:415)
[error] org.quartz.impl.DirectSchedulerFactory.createScheduler(DirectSchedulerFactory.java:372)
[error] org.quartz.impl.DirectSchedulerFactory.createScheduler(DirectSchedulerFactory.java:330)
[error] org.quartz.impl.DirectSchedulerFactory.createScheduler(DirectSchedulerFactory.java:298)
[error] com.typesafe.akka.extension.quartz.QuartzSchedulerExtension.scheduler$lzycompute(QuartzSchedulerExtension.scala:381)
[error] com.typesafe.akka.extension.quartz.QuartzSchedulerExtension.scheduler(QuartzSchedulerExtension.scala:378)
[error] com.typesafe.akka.extension.quartz.QuartzSchedulerExtension.<init>(QuartzSchedulerExtension.scala:73)
[error] sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[error] sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
[error] sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
[error] akka.actor.ReflectiveDynamicAccess.$anonfun$createInstanceFor$1(ReflectiveDynamicAccess.scala:32)
[error] akka.actor.ReflectiveDynamicAccess.createInstanceFor(ReflectiveDynamicAccess.scala:27)
[error] akka.actor.ExtensionKey.createExtension(Extension.scala:154)
[error] akka.actor.ActorSystemImpl.registerExtension(ActorSystem.scala:913)
[error] akka.actor.ExtensionId.apply(Extension.scala:78)
[error] akka.actor.ExtensionId.apply$(Extension.scala:77)
[error] akka.actor.ExtensionKey.apply(Extension.scala:150)
[error] modules.QuartzSchedulerProvider.get(QuartzSchedulerModule.scala:51)
[error] modules.QuartzSchedulerProvider.get(QuartzSchedulerModule.scala:40)
[error] com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:81)
[error] com.google.inject.internal.BoundProviderFactory.provision(BoundProviderFactory.java:72)
[error] com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:61)
[error] com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:62)
[error] com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
[error] com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
[error] com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
[error] com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
[error] com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
[error] com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
[error] com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
[error] com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
[error] com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
[error] com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
[error] com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
[error] com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
[error] com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
[error] com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
[error] com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
[error] com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:205)
[error] com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:199)
[error] com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1085)
[error] com.google.inject.internal.InternalInjectorCreator.loadEagerSingletons(InternalInjectorCreator.java:199)
[error] com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:180)
[error] com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
[error] com.google.inject.Guice.createInjector(Guice.java:99)
[error] com.google.inject.Guice.createInjector(Guice.java:84)
[error] play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:185)
[error] play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:137)
[error] base.BaseSpecification$BaseContext.application(BaseSpecification.scala:39)
[error] base.BaseSpecification$BaseContext.application$(BaseSpecification.scala:39)
[error] base.ApiSpecification$ApiContext.application(ApiSpecification.scala:21)
[error] base.ApiSpecification$ApiContext.controller$lzycompute(ApiSpecification.scala:37)
[error] base.ApiSpecification$ApiContext.controller(ApiSpecification.scala:37)
[error] controllers.UserControllerSpec$$anon$2$$anon$1.delayedEndpoint$controllers$UserControllerSpec$$anon$2$$anon$1$1(UserControllerSpec.scala:22)
[error] controllers.UserControllerSpec$$anon$2$$anon$1$delayedInit$body.apply(UserControllerSpec.scala:17)
[error] play.api.test.WithApplication.$anonfun$around$2(Specs.scala:46)
[error] play.api.test.PlayRunners.$anonfun$running$2(Helpers.scala:75)
[error] play.api.test.PlayRunners.runSynchronized(Helpers.scala:52)
[error] play.api.test.PlayRunners.runSynchronized$(Helpers.scala:48)
[error] play.api.test.Helpers$.runSynchronized(Helpers.scala:604)
[error] play.api.test.PlayRunners.running(Helpers.scala:73)
[error] play.api.test.PlayRunners.running$(Helpers.scala:71)
[error] play.api.test.Helpers$.running(Helpers.scala:604)
[error] play.api.test.WithApplication.around(Specs.scala:46)
[error] play.api.test.WithApplication.delayedInit(Specs.scala:37)
[error] controllers.UserControllerSpec$$anon$2$$anon$1.<init>(UserControllerSpec.scala:17)
[error] controllers.UserControllerSpec$$anon$2.<init>(UserControllerSpec.scala:17)
[error] controllers.UserControllerSpec.$anonfun$new$2(UserControllerSpec.scala:16)
[info] Total for specification UserControllerSpec
[info] Finished in 4 seconds, 253 ms
[info] 1 example, 0 failure, 1 error
[error] Error: Total 1, Failed 0, Errors 1, Passed 0
[error] Error during tests:
[error] 	controllers.UserControllerSpec
[error] (Test / testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 7 s, completed 13-Apr-2018 9:37:15 AM

Any ideas of stuff I could look at?

Thanks

EDIT: Maybe the fact that the scheduler is being instantiated is a bit of a red flag. Perhaps I need to override the test configuration to turn it off?


#2

Yes, this is an annoying issue!

I fix this normally by creating a dedicated JobModule which contains the QuartzScheduler related bindings. Then I disable this module globally in my tests.


import java.time.{ Clock, Instant, ZoneId }

import net.codingwell.scalaguice.ScalaModule
import org.specs2.specification.Scope
import play.api.i18n.{ Lang, Messages, MessagesApi }
import play.api.inject.Injector
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.test.PlaySpecification
import play.api.{ Application, Configuration }

/**
 * A specification which contains some helpers.
 */
trait BaseSpecification extends PlaySpecification {

  /**
   * The base context.
   */
  trait BaseContext extends Scope {

    /**
     * The fake module used to instantiate the application.
     */
    def fakeModule: ScalaModule = new ScalaModule {
      def configure(): Unit = {}
    }

    /**
     * The application builder.
     */
    def applicationBuilder: GuiceApplicationBuilder = GuiceApplicationBuilder(
      loadConfiguration = { environment =>
        val config = Configuration.load(environment)
        val disabledModules = config.get[Seq[String]]("play.modules.disabled")
        config ++ Configuration(
          "play.modules.disabled" -> (disabledModules ++ List("modules.JobModule"))
        )
      })
      .overrides(fakeModule)

    /**
     * The application.
     */
    def application: Application = applicationBuilder.build()

    /**
     * The Guice injector.
     */
    def injector: Injector = application.injector

    /**
     * The Play lang.
     */
    def lang: Lang = Lang("en-US")

    /**
     * The Play messages.
     */
    implicit def messages: Messages = application.injector.instanceOf[MessagesApi].preferred(Seq(lang))

    /**
     * The current clock.
     */
    lazy val clock: Clock = Clock.fixed(Instant.now(), ZoneId.of("UTC"))
  }
}