Search code examples
scalaunit-testingmockitoscalatesthamcrest

How to verify a method gets called with an object whose some fields may be anyObject() while others have a specific value?


I'm using Mockito with ScalaTest. Consider this simplified example.

Model case class:

case class Batch(batchId: Long, 
                 timestamp: Option[LocalDateTime] = Some(LocalDateTime.now),
                 invoicesReceived: Option[Int])

In my test I'm mocking a class called BatchRepository which has a method with this signature:

def create(conn: Connection, batch: Batch): Long

Relevant bit of test code:

verify(batchRepository, times(1)).create(anyObject(),
  Batch(anyLong(), anyObject(), Matchers.eq(Some(1))))
)

The beef is: I'd like to verify that the code under test calls the mocked repository method with whatever Connection and a Batch instance with whatever id and timestamp, but invoicesReceived being exactly Some(1).

Using Mockito, is this possible at all, and if so, how?

The production code creates a new Batch which sets the timestamp to current moment, so I think it's pretty much impossible to create a real Batch object in the test for the verify() call with the exact same timestamp. So at least for the timestamp I'd need anyObject().

I tried many variations, like wrapping the whole Batch in Matchers.eq(), but I haven't found anything that works:

Invalid use of argument matchers! 2 matchers expected, 4 recorded [...]

I'd be happy to hear I'm using matchers all wrong if there turns out to be some alternative way to use Mockito to test what I want. 🙂

(I was having hard time writing a good name for this question; please edit or leave a comment if you understand what I'm asking and can express it more succinctly.)


Solution

  • The problem is you are trying to verify two calls at once - create and Batch.apply. Can't do that.

    One way to do what you want is ArgumentCaptor:

    val captor = ArgumentCaptor.forClass(classOf[Batch])
    verify(batchRepository).create(any(), captor.capture)
    captor.getValue should matchPattern {
      case Batch(_, _, Some(1)) => 
    }
    // or just `captor.getValue.infoReceived shouldBe Some(1)`