Monday, March 10, 2008

Mocking out the logger

I have a simple CachingService that is a wrapper for Ehcache.   I wanted to create a unit test out of CachingService, since it doesn't have any dependencies.  However after running the test I was getting an error:  "groovy.lang.MissingPropertyException:  No such property: log for class:  CacheService".

Oops I forgot I have some logging inside my CachingService class.   At runtime Grails auto-magically injects a "log" property into service classes.  If my test was an integration test then there wouldn't be a problem.   So I have two options, either mock out the logger or run my test as an integration tests.   I decided to mock out the logger, which required me to do the following:

1.  Create a MockLogger class (primarily since I will probably have to mock out the logger in other classes in the future)

class MockLogger {
  void info(message) {}
  void warn(message) {}
  void debug(message) {}
  void error(message) {}
}

2.  Inject my MockLogger as a property named "log" via getLog() into my service class via ExpandoMetaClass

void setUp() {
  CachingService.metaClass.getLog = { -> new MockLogger() }
}

3.  Reset the state of CachingService before dynamically adding the property "log"

void tearDown() {
  def remove = GroovySystem.metaClassRegistry.&removeMetaClass
  remove CachingService
}

There may be other ways to mock out the logger, but this approach seemed simple enough.  Do you have an easier approach?  If so please drop a comment.





3 comments:

Unknown said...

Nice post Scott. I had to deal with this recently too and used an Expando for the mock log. Saves a class def. Something like:

def logger = new Expando( debug: { println it }, info: { println it },
warn: { println it }, error: { println it } )
YourService.metaClass.getLog = { -> logger }

Though, obviously you could drop the printlns if you don't want to see the logs.

Just another technique..

Unknown said...

I ended up doing this in the setup...

BasicConfigurator.configure()
LogManager.rootLogger.level = Level.DEBUG
log = LogManager.getLogger("MyService")
// use groovy metaClass to put the log
// into your class
MyService.class.metaClass.getLog << {-> log}

I blogged about it here:
logging in test

... don't know if that's very Groovy or not. Requires import of org.apache.log4j.*

Bill Comer said...

Scott,
I have tried this & it works great if I have one test.

If I have 2 tests in the same class then the second test gets its log messages printed out twice. Any thoughts please ?