In Grails 3 applications, logging is handled by the Logback logging framework.
Grails artefacts are configured with logging out of the box. The developer simply invokes
log.info("log whatever") and it works.
But how do we create unit tests that assert that logs occur with the appropriate level?
This blog will highlight some mock logging techniques and when to use them.
Default Grails Logging in Unit Tests
First let’s get the obvious out of the way for clarity. If you are not concerned about testing how logs operate in your application, there is no reason to worry about mocking loggers in Grails.
Since the logger is already present in Grails controllers, services, and domain objects there is no need to mock the logger when running unit tests. When a unit test is run with Grail Testing Support, the logger will execute just as in does in production mode. In this scenario, we can say the logger as already “mocked” for the purpose of focusing on the Class Under Test. This is distinct from the scenario of actually verifying logging occurs, which we will dive into next.
Verify Logging with Tests
What if we want to assert that a certain log occurs with a particular log level? For example, let’s say we want to test that
that the method below prints friendly advice based on the
In this scenario we need to mock the logger and capture the message passed in. Then we can assert that the message is correct and also assert the appropriate log level was used in each scenario. Conceptually, this is pretty easy. But in practice not so much.
Why not use Spock
One would think that we should simply mock out the logger with
def mockLogger = Mock(Logger), then set the service in the unit test with
service.log = mockLogger. We could proceed to check the arguments passed in and also the number of times
service.log is called with spock programming.
However, in Grails we run into a few basic problems while trying to mock with native spock or even
Logback is the default framework and the Logger is
final. We cannot
mock a final class. Furthermore, the injected
log property in Grails artefacts is a read-only property and cannot be set. These two fundamental
problems prohibit spock mocks from being effective in most mock logging situations in Grails.
Use Mockito to Verify Log Events
The Mockito Library can be used to verify logs take place. With Mockito, we can create a mock
Appender class, attach it to the
logger, and then use an
ArgumentCaptor to capture the invocations sent to the logger.
Spock test with Mockito
Use Slf4j Test to Verify Log Events
Slf4j Test is a test implementation of Slf4j that stores log messages in memory and provides messages for retrieving them. This works nicely to substitute for the real implementation in the test environment of Grails.
build.gradle, we first need to depend on the jar, and then exclude the real implementation from the test environment.
It is quite simple to check the logging events.
Spock test with Slf4j Test
Use Spock mocks with a declared logger
Spock mocks can be used on one particular case: with your own declared logger variable. If defined as non-final, you can use spock mocks to verify log calls. This approach is straightforward, but has the drawback that each class will have repeated code and does not rely on Grails conventions.
For example, use a
LoggerFactory to define a logger in a class.
Then a spock Specification with
Mock() follows the normal spock conventions to verify invocations and parameters.
Sample code of Grails mocking logging can be found here. I hope these examples can help you decide on the best approach for your project.