Hot questions for Using Mockito in 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