ArgumentCaptor – ask what is passed by
ArgumentCaptor is a nice Mockito feature that can be used to check and validate arguments passed by inside tested portion of code. With that feature we can see what happens internally – we can check ‘what’s inside the box.’
Background
Let’s suppose you have several kinds of messages you are passing by in your system and you want to persist corresponding types of event in something we would call an event log. That will be our model for Event
:
sealed trait Event case object EventA extends Event case object EventB extends Event case object EventC extends Event case object EventUnknown extends Event
We also have conversion from messages into Event
types:
object EventCreator { def fromMessage: String => Event = { case "messageA" => EventA case "messageB" => EventB case "messageC" => EventC case _ => EventUnknown } }
So whenever somewhere you receive “messageA” statement you want to store corresponding event. As it was said before – events will be stored in the event log:
trait EventRepository { def save(event: Event): Future[Unit] }
Use case
We have a trait defined that describes EventPersistor
behavior. An implementation should – for given String message – persist appropiate Event
in the event log:
import scala.concurrent.Future trait EventPersistor { def persistFor(message: String): Future[Unit] }
Let us provide example implementation. We will use the conversion from EventCreator
:
import [...].EventCreator.fromMessage import scala.concurrent.Future class EventPersistorImpl(eventRepository: EventRepository) extends EventPersistor { override def persistFor(message: String): Future[Unit] = { val event = fromMessage(message) eventRepository.save(event) } }
As you can see we create event in val event = fromMessage(message)
and persist in eventRepository.save(event)
. Those two lines, especially one with the save invocation, are crucial as we hope that correct event will be created and then passed by to function. Still nothing complicated happens here.
Testing
Using AsyncWordSpec
we can easily test whether our implementation really works. First of all, we have to mock our event log. Mockito smoothly help us:
private val mockedEventRepo = mock[EventRepository] when(mockedEventRepo.save(any[Event])) .thenReturn(Future.successful())
Then, we create an instance of our implementation:
private val testedEventPersistorImpl = new EventPersistorImpl(mockedEventRepo)
Actual testing would look like:
testedEventPersistorImpl .persistFor("messageA") .map { result => result shouldBe (()) }
Is that’s all? Well, not really… Cautious reader should notice that if above test passes it will only mean some kind of Event
has been successfully saved. But we have no clue what exactly type of event was internally persisted. Actually, above test will pass for any message converted to any Event
. That’s definitely unwanted feature.
Star of the evening
Here ArgumentCaptor
comes to the rescue. Firstly, we create instance of captor:
private val captor: ArgumentCaptor[Event] = ArgumentCaptor.forClass(classOf[Event])
Then, with a helper function:
private def checkPassedEvent(message: String, expectedEvent: Event) = { testedEventPersistorImpl .persistFor(message) .map { result => result shouldBe (()) verify(mockedEventRepo, atLeastOnce) .save(captor.capture()) val passedEvent = captor.getValue passedEvent shouldBe expectedEvent } }
we can finally test that our implementation really persists correct events:
checkPassedEvent(message = "messageA", expectedEvent = EventA) checkPassedEvent(message = "messageB", expectedEvent = EventB) checkPassedEvent(message = "messageC", expectedEvent = EventC) checkPassedEvent(message = "someMessage", expectedEvent = EventUnknown)
Reference
I provided you a lot of code snippets and not everything may be obvious at first reading. In case something is not clear enough or still doesn’t work you can get complete above example here: https://github.com/eltherion/mockito-argumentcaptor. Fork/clone it in order to play around and understand.