【问题标题】:Mocking a queue in Kotlin with Mockito does not seem to work用 Mockito 在 Kotlin 中模拟队列似乎不起作用
【发布时间】:2019-11-22 09:38:52
【问题描述】:

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

@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)
...

【问题讨论】:

  • 绝对没有理由模拟队列,也就是说,顺便说一句,不是服务的注入依赖项。
  • 不明白为什么不应该有理由模拟队列。我需要填充它来测试填充队列的行为。不嘲笑它:我应该如何填充它?而且因为它没有被注入,所以我不使用@InjectedMock,而是使用@Mock
  • 所以,填写它。让你的模拟 repository.repository.findTop1000ByImageTypeAndImageState() 方法返回一个非空列表,调用 getNextOrderForm() 将填满队列。你的测试应该验证方法的约定,即方法的每次调用都返回集合的下一个元素,直到没有下一个元素,然后再次获取集合。
  • 这完全不是我想要的。我已经介绍了那个测试用例。无需调用存储库即可填充队列。
  • 不,不能。您发布的代码中唯一填充私有队列的位置是存储库返回的集合。

标签: kotlin junit mockito junit5


【解决方案1】:

您的队列未标记为 @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 只会使用一种注入方法。因此,您不能将构造函数初始化与属性设置器或字段注入混合在一起。

还可以查看@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 为您所做的相同(具有相同的性能影响)。不过你也可以自己清理对象。

【讨论】:

  • 你是说没有自动装配的成员变量没有办法模拟它?我的意思是,当我在多个服务中需要相同的队列时,一个 bean 可能对队列有意义。但只要不是这种情况,这似乎有点矫枉过正。
  • 肯定有办法做到这一点。我的回答是假设您希望将MockitoExtension 提供的功能与其注释结合使用,并尽可能接近您提供的示例。您仍然可以手动创建模拟并手动注入模拟。但是你必须处理该变量的可访问性。从封装的角度来看,尤其是当你想独立于队列进行测试时,注入队列应该更合适。
  • 我明白了。我已经在使用注释和MockitoExtension,你是对的。但我真的很好奇模拟如何对简单的 memember 变量起作用。
  • github.com/nhaarman/mockito-kotlin 有一些很好的例子。基本上,您只需在测试中创建一个模拟变量并将其分配给该字段 - 只要它有一个 setter。
  • 另一种选择是使队列可公开访问,将其设为 var,然后执行以下操作:service.queue = mock {}。但由于这会暴露服务的内部结构,我不会在生产代码中这样做。
猜你喜欢
  • 2020-07-05
  • 1970-01-01
  • 1970-01-01
  • 2017-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-27
  • 1970-01-01
相关资源
最近更新 更多