Reputation: 10609
I have a simple named service which uses a queue:
@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
}
}
While trying to unit test this I want to mock the queue:
@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)
}
}
But the queue is always empty. So I guess the queue is not getting mocked correctly. What am I doing wrong?
EDIT
Things I have tried:
...
var queue: Queue<OrderForm> = LinkedList()
...
Mockito.mock
:...
var queue = Mockito.mock(Queue::class.java)
`when`(queue.isEmpty()).thenReturn(false)
`when`(queue.poll()).thenReturn(expected)
...
Upvotes: 1
Views: 463
Reputation: 662
Your queue is not marked as @Autowired
or part of the constructor, thus Mockito cannot mock it.
In order to make this work (haven't verified it though), define your constructor like this:
@Named
class OrderFormService @Inject constructor(
private val repository: OrderFormRepository,
private val queue: Queue<OrderForm>
) { }
Now, in order to have the queue initialized in your regular program, you have to define a bean for it, something like:
@Configuration
class QueueConfiguration {
@Bean
fun queue() : Queue<OrderForm> = LinkedList()
}
Furthermore, you should keep in concern that @InjectMocks
only will use one injection method. So you can't mix constructor initialisation with property setters or field injection.
Also have a look at @MockBean
. This replaces the bean globally and can be more convenient to use. It has the drawback that it dirties the context, resulting in context reinitialization and potentially slower tests if they aren't sliced properly.
EDIT:
Another alternative would be to set the mock manually (samples not verified, hope they work work you). I recommend using https://github.com/nhaarman/mockito-kotlin to make the Mockito syntax more kotlinish.
Setting the mock manually requires to make the queue a publicly settable property:
@Named
class OrderFormService @Inject constructor(
private val repository: OrderFormRepository
) {
var queue: Queue<OrderForm> = LinkedList()
}
Then, you assign the mock in your test:
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
}
}
There's one issue with this though: depending on the framework you use, your OrderFormService
might be initialised only once. When setting the queue, you change the global object which might affect other tests. To mitigate this, @DirtiesContext
on your test will ensure that the whole context is rebuilt (which impacts test performance). This is more or less the same which @MockBean
would do for you (with the same performance impact). You can also clean the object yourself though.
Upvotes: 1