1

I have a Play! 2 application (Scala) and there I have some classes that need to perform logging when they get errors.

I would like to be able to unit test that these logging actions actually take place in the right conditions. To do so, I need to be able to mock the logger, but I am getting some issues with Mockito. What I have (simplified) looks like

import play.api.{ Logger, LoggerLike }

trait BaseService {
  val log: LoggerLike

  def fail(reason: String) {
    log.error(reason)
   }
}

object Service extends BaseService {
  val log = Logger
}

and then in the tests

import org.specs2.mutable._
import org.specs2.mock._
import services.BaseService

object Service extends BaseService with Mockito {
  val log = mock[play.api.Logger]

  def verify(key: String) = {
    there was one(log).error(key)
  }
}

class ServiceSpec extends Specification {
  "failures should be logged" in {
    Service.fail("foo")
    Service.verify("foo")
  }
}

But I get the error

[error]     NullPointerException: null (Logger.scala:43)
[error] play.api.LoggerLike$class.isErrorEnabled(Logger.scala:43)
[error] play.api.Logger.isErrorEnabled(Logger.scala:147)
[error] play.api.LoggerLike$class.error(Logger.scala:127)
[error] play.api.Logger.error(Logger.scala:147)
[error] services.BaseService$class.fail(Service.scala:19)
[error] Service$.fail(ServiceSpec.scala:11)
...

I tried adding

log.isErrorEnabled returns true
log.error(any[String]) returns {}

but then even the initialization of Service fails.

Andrea
  • 20,253
  • 23
  • 114
  • 183

1 Answers1

1

I'm not much of a Mockito expert, but I think your mock is malfunctioning here since the stack trace shows the actual play Logger methods being called. This is likely because you're trying to mock a Scala object here. If instead you mock the trait like

 val log = mock[play.api.LoggerLike]

you'll get past this specific error and on to the next problem - your verification doesn't work:

[error]   The mock was not called as expected: 
[error] Argument(s) are different! Wanted:
[error] loggerLike.error(
[error]     ($anonfun$apply$mcV$sp$1) <function0>
[error] );
[error] -> at Service$$anonfun$verify$1.apply$mcV$sp(ServiceSpec.scala:10)
[error] Actual invocation has different arguments:
[error] loggerLike.error(
[error]     ($anonfun$fail$1) <function0>
[error] );
[error] -> at services.BaseService$class.fail(x.scala:57)
[error]  (ServiceSpec.scala:17)

This is caused by the fact that log messages are passed as by-name arguments (and thus anonymous functions) instead of strings, see this question for further discussion.

Community
  • 1
  • 1
themel
  • 8,825
  • 2
  • 32
  • 31
  • Thank you, this was exactly the issue! Now it remains to be cleared how to work around pass-by-name. The solution in the question you posted looks a bit ugly, but hopefully I will be able to massage it into something more clear. – Andrea Aug 31 '12 at 09:10