8

I have a Groovy class annotated with @Slf4j, so it gets a private final Logger log field, whose usage I'd like to test. I want to continue using @Slf4j and not expose the log field any further just to enable testing.

I'm writing my tests using Spock 1.0 and tried to accomplish this using Spock's integration mocking and stubbing functionality. Global stubbing should help me intercept the LoggerFactory invocation to get the actual Logger instance, so my current guess is this:

LoggerFactory logFactory = GroovyStub(global: true)
logFactory.getLogger(_) >> Mock(Logger)
// create my @Slf4j-annotated object afterwards

Interestingly, the interception actually works, println confirms that the class actually gets an object Mock for type 'Logger' named 'dummy', but the second statement that instructs the stub to return a mock does not seem to catch. Instead the default stub behaviour returns yet another stub, that cannot be used for mocking of course:

org.spockframework.runtime.InvalidSpecException: Stub 'dummy' matches the following required interaction:

1 * plugin.log.warn(_)   (0 invocations)

Remove the cardinality (e.g. '1 *'), or turn the stub into a mock.

What do I need to change to let the stubbed LoggerFactory return a mock Logger?

orsg
  • 661
  • 1
  • 6
  • 15
  • For people not using `@slf4j`, this questions may be useful http://stackoverflow.com/q/24439151/239408 – xverges Nov 19 '15 at 17:06

4 Answers4

3

You need to set the private final log-field with reflection, as explained here: Unit testing of a class with StaticLoggerBinder

Community
  • 1
  • 1
Alexander Sagen
  • 4,028
  • 1
  • 18
  • 15
0

If you want to test the log output, why don't you let the logging framework do their work and test the outcome (a log file)? You would only need to wire the log output to a file that you access after the test has run.

This could possibly result in less brittle tests.

Luis Muñiz
  • 4,649
  • 1
  • 27
  • 43
  • How do I do this? I tried to use http://projects.lidalia.org.uk/slf4j-test/ but my classpath already contains an slf4j backend, so I cannot change this (and don't want to change it for all my tests). – orsg May 22 '15 at 08:37
0

You can use a slf4j test backend to assert logging behavior like spf4j-slf4j-test. Slf4j will pick up the first backend implementation in your classpath, which is why you need to have the test backend dependency listed first in your project dependencies. (in the test scope).

user2179737
  • 493
  • 3
  • 6
0

I also faced the same issue after upgrading JUnit version from 4 to 5. @Rule and ExternalResource classes are removed from JUnit5.

I tried below approach to mock SLF4J Logger to run the test cases. I used on Java reflect classes to remove "final" and "static" modifiers from Logger variables.

Utils.java

import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Utils {
  public Integer getAddition(Integer number1, Integer number2) {
    log.error("Returning value");
    return Integer.sum(number1, number2);
  }
}

UtilsTests.groovy

import org.slf4j.Logger
import spock.lang.Specification

import java.lang.reflect.Field
import java.lang.reflect.Modifier

class UtilsTests extends Specification {

    def setup() {
    }

    def "Result equals result"() {
        given:
        Utils utils = new Utils()
        Logger log = Mock(Logger.class)
        setFinalStaticField(Utils.class, "log", log);

        when:
        def result = utils.getAddition(Integer.valueOf(1), Integer.valueOf(2));

        then:
        1 * utils.log.error("Returning value")
    }

    def "setFinalStaticField"(Class<?> clazz, String fieldName, Object value) throws ReflectiveOperationException {

        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);

        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        field.set(null, value);
    }
}

Result:

Test                    Duration    Result
Result equals result    0.295s      passed
Dilip Dalwadi
  • 167
  • 2
  • 10