如何在 Spock 的私有静态最终变量中使用 mock?

How to use mock in a private static final variable in Spock?

假设我在 Java class 中有一个 private static final 变量,例如:

@Service
public class MyClass {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);
    ...
}

然后我有测试 class 比如:

class MyClassTest extends Specification {

  @Autowired
  MyClass sut

  def "Testing a private static final variable"() {
    given:
    sut.LOGGER = Mock(Logger)

    when:
    ...
}

如何将 Mock(Logger)sut.LOGGER 一起使用,其中 LOGGERMyClass class 中的 private static final?如果没有 final 关键字,测试将 运行 正常,但我希望它与 final 关键字一起使用。非常感谢任何建议。

假设我们不希望使用 Mockito 内联 mock maker 或其他 mocking 强大的工具,而只是使用 Spock 的 on-board 手段,我们可以使用另一种专门针对 Slf4j 的方法:使用现有的记录器而不是 mocking它,但添加一个模拟 appender,然后验证那个上的交互。唯一的要求是知道使用了哪个实际的日志记录工具,或者只是配置测试环境以使用特定的工具。让我们选择 LogBack。如果我们知道 Slf4J 记录到 LogBack,我们可以导入它 类 并将记录器转换为它。

Class 测试中:

我故意弄得复杂一点,所以后面我们可以测试正数、负数和零数的几种情况:

package de.scrum_master.Whosebug.q71235231;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClassWithLogger {
  private static final Logger LOGGER = LoggerFactory.getLogger(ClassWithLogger.class);

  public void logSomething(int i) {
    LOGGER.info("FYI, 3 * {} = {}", i, 3 * i);
    if (i < 0)
      LOGGER.warn("Be warned that {} is a negative number", i);
    if (i == 0)
      LOGGER.error("Using zero is forbidden");
  }
}

斯波克测试

package de.scrum_master.Whosebug.q71235231

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.spi.LoggingEvent
import ch.qos.logback.core.Appender
import spock.lang.Specification
import spock.lang.Unroll

class ClassWithLoggerTest extends Specification {
  @Unroll("test logger with number #number")
  def "test logger"() {
    given:
    // Groovy can simply access private fields, if we know their names
    Logger logger = ClassWithLogger.LOGGER
    def appender = Mock(Appender)
    logger.addAppender(appender)

    when:
    new ClassWithLogger().logSomething(number)

    then:
    1 * appender.doAppend({ LoggingEvent event ->
      event.level == Level.INFO &&
        event.formattedMessage == "FYI, 3 * $number = ${3 * number}"
    })
    (number == 0 ? 1 : 0) * appender.doAppend({ LoggingEvent event ->
      event.level == Level.ERROR &&
        event.formattedMessage == "Using zero is forbidden"
    })
    (number < 0 ? 1 : 0) * appender.doAppend({ LoggingEvent event ->
      event.level == Level.WARN &&
        event.formattedMessage == "Be warned that $number is a negative number"
    })

    cleanup:
    logger.detachAppender(appender)

    where:
    number << [4, 1, 0, -1, -7]
  }
}

(number < 0 ? 1 : 0) * ...这样的动态交互计数可读性不强。我只是想展示 Spock 在测试参数化方面的能力,并在一个测试方法中涵盖所有场景。也许您最好将它分成 3 种方法并分别介绍每种情况。这样测试会更具可读性,但你也会有更多的重复代码。这是一个品味问题,我把选择权交给你。


更新: 如果您希望在 where: 部分计算更多动态内容(也可以在 given: 中完成),您可以使用这个变体(只是化妆品,逻辑不变):

class ClassWithLoggerTest extends Specification {
  @Unroll("test logger with number #number")
  def "test logger"() {
    given:
    // Groovy can simply access private fields, if we know their names
    Logger logger = ClassWithLogger.LOGGER
    def appender = Mock(Appender)
    logger.addAppender(appender)

    when:
    new ClassWithLogger().logSomething(number)

    then:
    infoCount * appender.doAppend({ LoggingEvent event ->
      event.level == Level.INFO && event.formattedMessage == infoMessage
    })
    errorCount * appender.doAppend({ LoggingEvent event ->
      event.level == Level.ERROR && event.formattedMessage == errorMessage
    })
    warningCount * appender.doAppend({ LoggingEvent event ->
      event.level == Level.WARN && event.formattedMessage == warningMessage
    })

    cleanup:
    logger.detachAppender(appender)

    where:
    number << [4, 1, 0, -1, -7]
    infoCount = 1
    infoMessage = "FYI, 3 * $number = ${3 * number}"
    errorCount = number == 0 ? 1 : 0
    errorMessage = "Using zero is forbidden"
    warningCount = number < 0 ? 1 : 0
    warningMessage = "Be warned that $number is a negative number"
  }
}

来源: 博客 post "Don't mock static: test SLF4J Logger with appenders",改编为 Spock。