2

Say I have something like this (VERY OVER-SIMPLIFIED):

case class Foo(bar: String)
val mockFnThatTakesFoo = mock[Foo => Unit]

def fooHasBarSetTo(expectedBar: String, foo: Foo): Boolean = {
  val actualBar = foo.bar
  actualBar shouldEqual expectedBar
  true
}

mockFnThatTakesFoo(argThat(fooHasBarSetTo("blah", _))) wasCalled once

This works. However, the assertion itself is a little bit convoluted and it could be made more readable.
I tried this:

val withFooHavingBarSetTo = (expectedBar: String) => argThat(fooHasBarSetTo(expectedBar, _))

//and then
mockFnThatTakesFoo(withFooHavingBarSetTo("blah")) wasCalled once

Much neater! but doesn't work :/

> [info] FooSpec:
[info] - should do stuff *** FAILED ***
[info]   org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Invalid use of argument matchers!
[info] 1 matchers expected, 2 recorded:
[info] -> at com.foo.FooSpec.argThat(FooSpec.scala:28)
[info] -> at com.foo.FooSpec.$anonfun$new$5(FooSpec.scala:204)

Any idea how can this be done?

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
Daniel Gruszczyk
  • 5,379
  • 8
  • 47
  • 86
  • I am not sure I understand what you are trying to test. It seems that fooHasBarSetTo returns a Boolean, while mockFnThatTakesFoo takes a foo. How does it compile? Can you please elaborate what you are trying to test? You can try using the verify method of mockito. https://www.programcreek.com/scala/org.mockito.Mockito.verify – Tomer Shetah Jul 29 '20 at 17:42
  • `fooHasBarSetTo` is a function to help assert on arguments passed to `mockFnThatTakesFoo`. That's how mokito's `argThat` works, it takes a function that takes an argument that the tested function (`mockFnThatTakesFoo`) would, and returns true to pass or false to fail the assertion. https://site.mockito.org/javadoc/current/org/mockito/ArgumentMatcher.html – Daniel Gruszczyk Jul 30 '20 at 08:39
  • Like I said, this is very oversimplified. In practice I am using this approach to test mocked finagle service that my service depends on. So the `fooHasBarSetTo` equivalent would take arguments that the service mock was called with, deserialize body buffers etc and do more detailed assertions on this. – Daniel Gruszczyk Jul 30 '20 at 08:43
  • You are right. But argThat, at least in scala, takes an ArgumentMatcher which you do not override. I an bit sure I understand the scenario you are testing. What is the method you are testing? – Tomer Shetah Jul 30 '20 at 08:43
  • `argThat` can take a function that simply accepts an argument and returns a boolean. Check lower down in docs. – Daniel Gruszczyk Jul 30 '20 at 08:43
  • here's the function signature: `def argThat[T](f: T => Boolean, desc: => String = "argThat()"): T` – Daniel Gruszczyk Jul 30 '20 at 08:45
  • Can you please share your imports? I can't find that method you are using. I can't find either the wasCalled method. – Tomer Shetah Jul 30 '20 at 08:48
  • It's from https://github.com/mockito/mockito-scala and IdiomaticMockito trait in particular. `import org.mockito.scalatest.IdiomaticMockito` – Daniel Gruszczyk Jul 30 '20 at 09:17

1 Answers1

0

I believe Mockito is implemented using macro and it tracks positions where argThat and other Mockito methods are placed. For example if you try to create a variable like

    val blahBar = argThat(fooHasBarSetTo("blah", _))

You will get an exception "Misplaced or misused argument matcher detected here". So it is impossible to return argThat matcher from another method.

If you need to reuse mock matcher with different argument value I see only one possible solution like

    def mockCalledOnceWithFooHavingBar(expectedBar: String) {
      mockFnThatTakesFoo(argThat(fooHasBarSetTo(expectedBar, _))) wasCalled once
    }

    mockCalledOnceWithFooHavingBar("blah")
    mockCalledOnceWithFooHavingBar("bar")
Yuriy Tumakha
  • 1,450
  • 17
  • 21