Hot questions for Using Mockito in playframework 2.4

Top 10 Java Open Source / Mockito / playframework 2.4

Question:

When trying to unit test my code (with Mockito) that runs an Akka scheduler I keep getting "cannot enqueue after timer shutdown".

My code:

Global.scala

override def onStart(app: Application){
    Some(Akka.system.scheduler.schedule(23.hours, 24.hours) {
         println("I run all the time")
    })
}

TasksRepositorySpec.scala

def insertTestData() = {
    running(FakeApplication()) {
       //some code
    }
}

When I run:

activator test

I get:

[info] TasksRepositorySpec
[info] 
[error] ! 
[error]  cannot enqueue after timer shutdown (Scheduler.scala:270)
[error] akka.actor.LightArrayRevolverScheduler.schedule(Scheduler.scala:270)
[error] akka.actor.Scheduler$class.schedule(Scheduler.scala:79)
[error] akka.actor.LightArrayRevolverScheduler.schedule(Scheduler.scala:182)
[error] Global$.onStart(Global.scala:56)
[error] play.api.GlobalPlugin.onStart(GlobalSettings.scala:272)
[error] play.api.Play$$anonfun$start$1$$anonfun$apply$mcV$sp$1.apply(Play.scala:91)
[error] play.api.Play$$anonfun$start$1$$anonfun$apply$mcV$sp$1.apply(Play.scala:91)
[error] play.api.Plugins.foreach(Plugins.scala:57)
[error] play.api.Play$$anonfun$start$1.apply$mcV$sp(Play.scala:91)
[error] play.api.Play$$anonfun$start$1.apply(Play.scala:91)
[error] play.api.Play$$anonfun$start$1.apply(Play.scala:91)
[error] play.utils.Threads$.withContextClassLoader(Threads.scala:21)
[error] play.api.Play$.start(Play.scala:90)
[error] play.api.test.PlayRunners$class.running(Helpers.scala:41)
[error] play.api.test.Helpers$.running(Helpers.scala:363)
[error] repositories.TasksRepositorySpec.insertData(TasksRepositorySpec.scala:69)
[error] repositories.TasksRepositorySpec$$anonfun$2.apply$mcI$sp(TasksRepositorySpec.scala:88)
[error] repositories.TasksRepositorySpec$$anonfun$2.apply(TasksRepositorySpec.scala:88)
[error] repositories.TasksRepositorySpec$$anonfun$2.apply(TasksRepositorySpec.scala:88)

I use play 2.4

Any help will be appreciated


Answer:

Sorry I didn't find a solution, but only a workaround. It consists in using a fake Global object for tests where onStart() and onStop() methods are empty.

First you need to split your Global object into a trait and an empty object:

Global.scala

object Global extends Global

trait Global extends GlobalSettings { ... }

Then in your test code, you can create a fake Global:

Fake.scala

object FakeGlobal extends Global {
  override def onStart(app: Application): Unit = {}
  override def onStop(app: Application): Unit = {}
}

object Fake {
  // This has to be a method, cannot be a value
  def application() = FakeApplication(withGlobal = Some(FakeGlobal))
}

And finally use it in your tests:

TasksRepositorySpec.scala

def insertTestData() = {
  running(Fake.application()) {
    //some code
  }
}

It works for me. Hope it helps!

Question:

I am migrating my Java play application from 2.37 -> 2.4.1. In my controller unit tests, I had set up the controller along with its associated mocked dependencies before each test.

It seems that the getControllerInstance method was removed from GlobalSettings in Play 2.4 so now I can't override it to return my controller instance.

@RunWith(MockitoJUnitRunner.class)
public class PublicRoomsControllerTest extends WithApplication {

    @Mock
    private MyService myService;

    private MyController myController;

    @Before
    public void setUp() {
        myController = new MyController(myService);

        GlobalSettings global = new GlobalSettings() {

            public <T> T getControllerInstance(Class<T> clazz) {
                return (T) controller;
            }

        };

        start(fakeApplication(global));
    }

    @Test
    public void myTest() {
        Result result = route(new RequestBuilder().method(POST).uri("/test"));
        assertEquals(OK, result.status());
    }

}

I know I can call the method directly on my controller instance from my test such as:

Result result = myController.someMethod();
assertEquals(OK, result.status());

This approach seems to work fine until someMethod() relies on form data in the request like

 Map<String, String> data = Form.form().bindFromRequest().data();

Is there someway for the test to route requests, which may include form data, to use my controller instance?

(I am using Guice, Mockito, and JUnit)


Answer:

As pointed out to me here https://github.com/playframework/playframework/issues/4876. The correct way to do this in Play 2.4.x is to use Helpers.invokeWithContext. So to test my controller with my mocked dependencies I used the following code:

RequestBuilder requestBuilder = new RequestBuilder().bodyForm(ImmutableMap.of("userId", 1));
Result result = new Helpers().invokeWithContext(requestBuilder, () -> myController.someMethod());

Side note: I beleive invokeWithContext is being changed to a static method in the future.

Question:

I want to mock a method taking a callback in argument let's say:

methodToMock[T](callback: (String, String) => T)

With play 2.3, (specs2 version 2.3.8) I managed to do it with the method doAnswer from mockito:

doAnswer({ invocation =>
  val method = invocation.asInstanceOf[(String, String) => Any] // L.34
  method(role, key)
}).when(myMock).methodToMock[Any](any)

But since play 2.4 (using special dependency specs2, version 2.4.2), the previous code won't work, telling me:

[Ljava.lang.Object; cannot be cast to scala.Function2 (MySpec.scala:34)

I don't really understand why mockito start using Java objects in my code, since I'm using the specs2 implementation and didn't find any documentation about modifications on doAnswer, nor usage example of my use case.

Do you have any idea of what I did wrong and a way to solve this?

EDIT:

I wanted to show a simplified case, but that deleted the source of the problem... The real definition of the method also takes an implicit arguments list:

methodToMock[T](callback: (String, String) => T)(implicit value: String)

Which means that specs2 seems to return an array instead of a single element (its behaviour really changed between the two versions though).

The following code now works

doAnswer({ invocation =>
  val firstArgList = invocation.asInstanceOf[Array[Object]](0)
  val method = firstArgList.asInstanceOf[(String, String) => Any] // L.34
  method(role, key)
}).when(myMock).methodToMock[Any](any)(any)

Answer:

Which version of specs2 are you using? With 3.6.5 (the latest) the following works fine

case class T() {
  def methodToMock[A](callback: (String, String) => A) = 1
}

val m = mock[T]

doAnswer({ invocation =>
  val method = invocation.asInstanceOf[(String, String) => Any] // L.34
  method("role", "key")
}).when(m).methodToMock[Any](any)

m.methodToMock((s: Any, s2: Any) => s.toString.size + s2.toString.size) === 7