使用 Mockito 在 Kotlin 中模拟队列似乎不起作用

Mocking a queue in Kotlin with Mockito does not seem to work

我有一个使用队列的简单命名服务:

@Named
class OrderFormService @Inject constructor(
        private val repository: OrderFormRepository
) {
    private val queue: Queue<OrderForm> = LinkedList()

    private val logger: Logger = LoggerFactory.getLogger("service")

    fun getNextOrderForm(input: GetNextOrderFormInput): GetNextOrderFormPayload? {
        if (queue.isEmpty()) {
            logger.info("queue is empty")

            val forms: List<OrderForm> = repository.findTop1000ByImageTypeAndImageState(input.type, input.state)

            forms.forEach {
                queue.offer(it)
            }
        }

        if (!queue.isEmpty()) {
            return GetNextOrderFormPayload(queue.poll())
        }

        return null
    }
}

在尝试对此进行单元测试时,我想模拟队列:

@ExtendWith(MockitoExtension::class)
internal class OrderFormServiceTest {

    @Mock
    private val queue: Queue<OrderForm> = LinkedList()

    @Mock
    lateinit var repository: OrderFormRepository

    @InjectMocks
    lateinit var service: OrderFormService


    @Test
    fun givenValidInputAndFilledQueueWhenGetNextOrderFormThenReturnPayload() {
        // given
        val expected = createOrderForm()
        val expectedPayload = GetNextOrderFormPayload(expected)

        given(queue.isEmpty()).willReturn(false)
        given(queue.poll()).willReturn(expected)

        // when
        val input = GetNextOrderFormInput(ImageType.NUMBER, ImageState.UNCLASSIFIED)
        val result = service.getNextOrderForm(input)

        // then
        assertThat(result).isEqualTo(expectedPayload)
    }
}

但是队列总是空的。所以我猜队列没有被正确模拟。我做错了什么?

编辑

我尝试过的事情:

  1. 使队列不是最终队列:
...
var queue: Queue<OrderForm> = LinkedList()
...
  1. 使用Mockito.mock:
...
var queue = Mockito.mock(Queue::class.java)

`when`(queue.isEmpty()).thenReturn(false)
`when`(queue.poll()).thenReturn(expected)
...

您的队列未标记为 @Autowired 或构造函数的一部分,因此 Mockito 无法模拟它。

为了完成这项工作(虽然还没有验证),像这样定义你的构造函数:

@Named
class OrderFormService @Inject constructor(
    private val repository: OrderFormRepository,
    private val queue: Queue<OrderForm>
) { }

现在,为了在常规程序中初始化队列,您必须为其定义一个 bean,例如:

@Configuration
class QueueConfiguration {
    @Bean
    fun queue() : Queue<OrderForm> = LinkedList()
}

此外,您应该注意 @InjectMocks 只会使用一种注入方法。因此,您不能将构造函数初始化与 属性 setter 或字段注入混合使用。

也可以看看 @MockBean。这会在全局范围内替换 bean,使用起来会更方便。它的缺点是会弄脏上下文,导致上下文重新初始化,如果没有正确切片,测试速度可能会变慢。

编辑:

另一种选择是手动设置模拟(示例未验证,希望它们对您有用)。我建议使用 https://github.com/nhaarman/mockito-kotlin 使 Mockito 语法更 kotlinish。

手动设置模拟需要使队列可公开设置属性:

@Named
class OrderFormService @Inject constructor(
        private val repository: OrderFormRepository
) {
    var queue: Queue<OrderForm> = LinkedList()
}

然后,您在测试中分配模拟:

internal class OrderFormServiceTest {

    private val queue: Queue<OrderForm> = mock {}

    @Mock
    lateinit var repository: OrderFormRepository

    @InjectMocks
    lateinit var service: OrderFormService

    @BeforeEach
    fun setup() {
        service.queue = queue
    }
}

虽然这有一个问题:根据您使用的框架,您的 OrderFormService 可能只初始化一次。设置队列时,您更改了可能影响其他测试的全局对象。为了缓解这种情况,测试中的 @DirtiesContext 将确保重建整个上下文(这会影响测试性能)。这或多或少与 @MockBean 为您所做的相同(具有相同的性能影响)。不过,您也可以自己清理对象。