Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I verify invokations with specific string matchers in Specs2 with Mockito

I have a test along these lines:

httpClient.post(anyString, anyString) returns (first, second)

//do my thing

there were two(httpClient).post(anyString, anyString)

This works fine, but I want to verify that the first call passes a different body than the second call. The body is rather big and I don't want to do precise matching on a strict example. I've tried this:

there was one(httpClientMock).postMessage(anyString, argThat(contain("FOO"))
there was one(httpClientMock).postMessage(anyString, argThat(contain("FOO"))

That makes Mockito complain:

InvalidUseOfMatchersException: 
 [error] Invalid use of argument matchers!
 [error] 2 matchers expected, 3 recorded:

I've also tried:

  there was one(httpClientMock).postMessage(argThat(contain("foo")), argThat(contain("FOO")))
  there was one(httpClientMock).postMessage(argThat(contain("foo")), argThat(contain("FOO")))

which results in:

Wanted 1 time:
 [error] -> ...
 [error] But was 2 times. Undesired invocation: ...

It seems to me that something like this should be possible, but I can't seem to figure it out. Insights?

like image 680
iwein Avatar asked Mar 02 '13 10:03

iwein


People also ask

What can I use instead of Mockito matchers?

Since Mockito any(Class) and anyInt family matchers perform a type check, thus they won't match null arguments. Instead use the isNull matcher.

What is argument matcher in Mockito?

Argument matchers are mainly used for performing flexible verification and stubbing in Mockito. It extends ArgumentMatchers class to access all the matcher functions. Mockito uses equal() as a legacy method for verification and matching of argument values.


1 Answers

I think that this is more of a problem with Mockito to begin with. When you're using Mockito with specs2 and you're in doubt, always drop down to the direct Mockito API:

// simplified httpClient with only one parameter
val httpClient = mock[HttpClient]
httpClient.post(anyString) returns ""

httpClient.post("s1")
httpClient.post("s2")

// forget specs2
// there was two(httpClient).post(anyString)

org.mockito.Mockito.verify(httpClient, org.mockito.Mockito.times(1)).post("s1")

// I guess that you don't want this to pass but it does
org.mockito.Mockito.verify(httpClient, org.mockito.Mockito.times(1)).post("s1")

One possible way around this is to define a matcher that will check the successive values of an argument:

there was two(httpClient).post(consecutiveValues(===("s1"), ===("s2")))

And the consecutiveValues matcher is defined as such:

import matcher._
import MatcherImplicits._

// return a matcher that will check a value against a different
// `expected` matcher each time it is invoked
def consecutiveValues[T](expected: Matcher[T]*): Matcher[T] = {
  // count the number of tested values
  var i = -1

  // store the results
  var results: Seq[(Int, MatchResult[T])] = Seq()

  def result(t: T) = {
    i += 1
    // with Mockito values are tested twice
    // the first time we return the result (but also store it)
    // the second time we return the computed result
    if (i < expected.size) {
      val mr = expected(i).apply(Expectable(t))
      results = results :+ (i, mr)
      mr
     } else results(i - expected.size)._2
  }

  // return a function that is translated to a specs2 matcher
  // thanks to implicits
  // display the failing messages if there are any
  (t: T) => (result(t).isSuccess,
             results.filterNot(_._2.isSuccess).map { case (n, mr) => 
               s"value $n is incorrect: ${mr.message}" }.mkString(", "))
}

You can test the code above. The failure messages are not the best but do the trick. In this situation:

httpClient.post("s1") 
httpClient.post("s2")

there was two(httpClient).post(consecutiveValues(===("s1"), ===("s3")))

You will see:

[error] x test
[error]  The mock was not called as expected: 
[error]  httpClient.post(
[error]      value 1 is incorrect: 's2' is not equal to 's3'
[error]  );
[error]  Wanted 2 times:
[error]  -> at ... 
[error]  But was 1 time:
[error]  -> at ...
like image 116
Eric Avatar answered Sep 21 '22 11:09

Eric