使用 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)
}
}
但是队列总是空的。所以我猜队列没有被正确模拟。我做错了什么?
编辑
我尝试过的事情:
- 使队列不是最终队列:
...
var queue: Queue<OrderForm> = LinkedList()
...
- 使用
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
为您所做的相同(具有相同的性能影响)。不过,您也可以自己清理对象。
我有一个使用队列的简单命名服务:
@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)
}
}
但是队列总是空的。所以我猜队列没有被正确模拟。我做错了什么?
编辑
我尝试过的事情:
- 使队列不是最终队列:
...
var queue: Queue<OrderForm> = LinkedList()
...
- 使用
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
为您所做的相同(具有相同的性能影响)。不过,您也可以自己清理对象。