9

So I have a class that has a method that logs a message:

class Car {
    private Logger logger = LoggerFactory.getLogger(Car.class);


    void startCar() {
        logger.error("car stopped working");
    }
}

How can I test that the error was logged using the spock testing framework?

class CarTest extends Specification {
    def "test startCar"() {
        given:
        Car newCar = new Car();

        when:
        newCar.startCar();

        then:
        // HOW CAN I ASSERT THAT THE MESSAGE WAS LOGGED???
    }
}
letter Q
  • 14,735
  • 33
  • 79
  • 118

2 Answers2

17

you could check for an invocation of error on the logger

@Grab(group='org.spockframework', module='spock-core', version='0.7-groovy-2.0')
@Grab(group='org.slf4j', module='slf4j-api', version='1.7.7')
@Grab(group='ch.qos.logback', module='logback-classic', version='1.1.2')

import org.slf4j.Logger

class MockLog extends spock.lang.Specification {

    public class Car {
        private Logger logger = org.slf4j.LoggerFactory.getLogger(Car.class);
        void startCar() {
            logger.error('car stopped working');
        }
    }

    def "mock log"() {
    given:
        def car = new Car()
        car.logger = Mock(Logger)
    when:
        car.startCar()
    then:
        1 * car.logger.error('car stopped working')
    }
}

edit: Full example https://github.com/christoph-frick/spock-test-logging

cfrick
  • 35,203
  • 6
  • 56
  • 68
  • 2
    `groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: LOG `. In your example you have access to car.logger - but how to this if the class is not within the Spock Testclass? – xetra11 Jun 12 '18 at 17:12
  • This example seems misleading. In a real life scenario, your logger would not be acccessible to your test code (class Car would not be inside the Specification) – matt freake Jun 29 '18 at 10:09
  • 1
    @mattfreake I added a link to a full example to show, that this does not rely on the class to be inside the test. This was done to have a short example. If you mean something else, please elaborate. If you are against writing tests like this, then I am with you - but please blame OP and not the answer. If you need something so badly logged, that it merits writing a test, then provide a proper infrastructure for it. If the test is hairy or hard to write, it's not the tests fault but the fault of the SUT. – cfrick Jun 30 '18 at 05:05
  • How does your car instance have access to the logger? isn't it private? – L. Dai Aug 07 '19 at 14:31
  • 1
    @L.Dai Groovy and/or Spock don't care. Also private on the jvm is not really private (can at least be accessed via reflection) – cfrick Aug 07 '19 at 14:38
  • What if you dont have Logger in the class as constructor dependency? If there is @Slf4j from lombok then? – jarvo69 Aug 18 '23 at 05:53
8

My Loggers are private static final so I cannot use solution mentioned above and rather not use Reflection.

If you are using Spring, you have acces to OutputCaptureRule.

@Rule
OutputCaptureRule outputCaptureRule = new OutputCaptureRule()

def test(){
outputCaptureRule.getAll().contains("<your test output>")
}
Sven Dhaens
  • 1,916
  • 1
  • 15
  • 10
  • Not only does this work with Spring, but it also works with Travis CI. Previously, `OutputCapture` was the class I would use in Spring/Spock, but it was deprecated and would fail the CI build. I'm glad I searched for another answer and found this! Also, I feel I should mention we're using `@Slf4j`, and this is perfect. Thanks! – randy Jun 05 '20 at 12:59
  • You may also need to add dependency `'org.spockframework:spock-junit4:2.0-groovy-3.0'` and also set the parameter `follow="true"` for your Console appender in the log4j2 configuration file (see https://stackoverflow.com/a/70350184/10592946) – BeshEater Jan 17 '22 at 16:56
  • Some important notes: 1 - the field must be public, or public static if it is a `@ClassRule` 2 - This is for Junit4, if you are using JUnit5 (which you very likely are if you are on springboot 2.6+) you need to use [OutputCaptureExtension](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/system/OutputCaptureExtension.html) instead as JUnit5 ignores `@Rules` altogether. Took me a while to figure this out. – Tocchetto Jun 20 '23 at 13:54