在 Kotlin 中使用 EasyMock 时,anyObject() 不得为 null

问题描述 投票:0回答:1

我正在使用 EasyMock 和 Kotlin。我尝试模拟一个示例类。

我不断收到的问题是

anyObject
,无论是否有特定的类,都会抛出
NullPointerException
,因为 Kotlin 在类型方面比 Java 更严格。

java.lang.NullPointerException:anyObject(Logger::class.java) 不能为 null

我使用简单的类实现运行的测试示例如下。

基于EasyMock的测试MyServiceMockTest.kt:

import org.easymock.EasyMock.anyObject
import org.easymock.EasyMock.anyString
import org.easymock.EasyMock.replay
import org.easymock.EasyMock.verify
import org.easymock.EasyMockExtension
import org.easymock.Mock
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.slf4j.Logger

@ExtendWith(EasyMockExtension::class)
class MyServiceMockTest {

    @Mock
    private lateinit var loggerService: LoggerService

    private lateinit var myService: MyService

    @BeforeEach
    fun setUp() {
        myService = MyService(loggerService)
    }

    @Test
    fun `should test logger implementation`() {
        loggerService.info(anyObject(Logger::class.java), anyString())
        replay(loggerService)

        myService.`generate different logs based on incoming numbers`(0)
        verify(loggerService)
    }
}

MyService.kt:

import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class MyService @Autowired constructor(val loggerService: LoggerService) {

    private val logger = LoggerFactory.getLogger(MyService::class.java)

    companion object {
        const val BREADCRUMB_ID = "fd8f6ac2-8d27-11ee-b9d1-0242ac120002"
    }


    fun `generate different logs based on incoming numbers`(num: Int) {
        when (num) {
            0 -> 
                loggerService.info(
                    logger = logger,
                    breadcrumbId = BREADCRUMB_ID
                )
            1 ->
                loggerService.info(
                    logger = logger,
                    breadcrumbId = BREADCRUMB_ID,
                    events = listOf("Log a single message")
                )
            2 ->
                loggerService.info(
                    logger = logger,
                    breadcrumbId = BREADCRUMB_ID,
                    events = listOf("Log a message"),
                    params = mapOf("num" to num)
                )
            3 ->
                loggerService.warn(
                    logger = logger,
                    breadcrumbId =  BREADCRUMB_ID,
                    params = mapOf("num warnings" to num)
                )
            else -> {
                loggerService.info(
                    logger = logger,
                    breadcrumbId = BREADCRUMB_ID
                )
                loggerService.warn(
                    logger = logger,
                    breadcrumbId = BREADCRUMB_ID
                )
                loggerService.error(
                    logger = logger,
                    breadcrumbId = BREADCRUMB_ID
                )
            }
        }
    }
}

LoggerService.kt:

import org.slf4j.Logger
import org.springframework.boot.logging.LogLevel
import org.springframework.boot.logging.LogLevel.ERROR
import org.springframework.boot.logging.LogLevel.INFO
import org.springframework.boot.logging.LogLevel.WARN
import org.springframework.stereotype.Service

/**
 * As this LoggerService unique per service,
 * be it one of the services in a monolith
 * or one of isolated services in microservice architecture
 * it has an extra field <code>breadcrumbId</code>, so
 * all the messages can be traced by this ID.
 */
@Service
open class LoggerService {
    fun info(logger: Logger, breadcrumbId: String, events: List<Any>? = null, params: Map<Any, Any?>? = null) {
        log(INFO, logger, breadcrumbId, events, params)
    }

    fun error(logger: Logger?, breadcrumbId: String?, events: List<Any>? = null, params: Map<Any, Any?>? = null) {
        log(ERROR, logger!!, breadcrumbId!!, events, params)
    }

    fun warn(logger: Logger, breadcrumbId: String, events: List<Any>? = null, params: Map<Any, Any?>? = null) {
        log(WARN, logger, breadcrumbId, events, params)
    }

    /**
     * The implementation is limited, it does not include TRACE, DEBUG modes.
     */
    private fun log(level: LogLevel, logger: Logger, breadcrumbId: String, events: List<Any>? = null, params: Map<Any, Any?>? = null) {
        /**
         * Builder behavior mimics MDC Logger.
         * It allows to have a greater flexibility, than some other available solutions.
         */
        val message = LogMessage(breadcrumbId)
            .addParams("events", events)
            .addParams("params", params)
            .build()
        when (level) {
            ERROR -> logger.error(message)
            WARN -> logger.warn(message)
            else -> logger.info(message)
        }
    }
}


/**
 * The implementation of this class can be further extended.
 *
 * Here is a simple reference implementation that can be used as it is.
 */
open class LogMessage {

    private val builder: StringBuilder

    constructor(breadcrumbId: String){
        builder = StringBuilder("[$breadcrumbId]")
    }

    fun addParams(name: Any, value: Any?): LogMessage {
        if (value != null) {
            builder.append(", $name: $value")
        }
        return this
    }

    fun build(): String {
        return builder.toString()
    }
}

我之前也遇到过与

anyString
类似的问题,但是通过
anyString
的功能扩展解决了这个问题,但是对于
anyObject
我无法想出类似的实现。

当前的测试有什么方法可以使其在没有

NullPointerException
功能或其他替代方案的情况下工作吗?

spring-boot kotlin logging mocking easymock
1个回答
0
投票

这是一个有趣的问题。因为 Kotlin 无法接受传递给无法接收 null 的方法的 null,所以它会变得疯狂。我做了一些阅读和实验。看来唯一的办法就是做这样的事情。

object Helper {
    fun <T> anyObject(item: Class<T>, result: T): T {
        EasyMock.anyObject(item)
        return result
    }

    fun anyString(): String {
        EasyMock.anyString()
        return ""
    }
}

@Test
fun `should test logger implementation`() {
    val result : Logger = mock(Logger::class.java)
    loggerService.info(Helper.anyObject(Logger::class.java, result), Helper.anyString())
    replay(loggerService)

    myService.`generate different logs based on incoming numbers`(0)
    verify(loggerService)
}

在这种情况下,

anyString
anyObject
不再返回
null
。我需要在参数中传递
result
而不是做

fun <T> anyObject(item: Class<T>): T {
    EasyMock.anyObject(item)
    return mock(item)
}

因为你无法在匹配器中创建模拟。它让 EasyMock 疯狂。

遗憾的是,当使用 EasyMock 5.2.0 和 Java 21 执行此操作时,模拟下的拦截器似乎无法正常工作。我们得到一个

java.lang.IllegalStateException: matcher calls were used outside expectations
。我不知道为什么,但这很可能是因为 Kotlin 为
LoggerService
生成了一个奇怪的类。如果您有相同的结果,您可以提交 EasyMock bug。

© www.soinside.com 2019 - 2024. All rights reserved.