29

I'm using mockito with scalatest. I have following problem when using matcher with value class.

import org.scalatest.FlatSpec
import org.scalatest.mock.MockitoSugar
import org.mockito.BDDMockito._
import org.mockito.Matchers.any

case class FirstId(val value: String) extends AnyVal
case class SecondId(val value: String) extends AnyVal

trait MockedClass {
  def someMethods(firstId: FirstId, secondId: SecondId): Int
}

class ValueClassSpec() extends FlatSpec with MockitoSugar {

  val mockedClass = mock[MockedClass]
  val secondId = SecondId("secondId")

  "Matchers" should "work for value class" in {
    // given
    given(mockedClass.someMethods(any[FirstId], org.mockito.Matchers.eq(secondId))).willReturn(3)
    // when
    val result = mockedClass.someMethods(FirstId("firstId"), secondId)
    // then
    assert(result == 3)
  }

}

and the result is:

ValueClassSpec:
Matchers
- should work for value class *** FAILED ***
  java.lang.NullPointerException:
  at io.scalac.fow.party.ValueClassSpec$$anonfun$1.apply$mcV$sp(ValueClassSpec.scala:22)
  at io.scalac.fow.party.ValueClassSpec$$anonfun$1.apply(ValueClassSpec.scala:20)
  at io.scalac.fow.party.ValueClassSpec$$anonfun$1.apply(ValueClassSpec.scala:20)
  at org.scalatest.Transformer$$anonfun$apply$1.apply(Transformer.scala:22)
  at org.scalatest.Transformer$$anonfun$apply$1.apply(Transformer.scala:22)
  at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)
  at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
  at org.scalatest.Transformer.apply(Transformer.scala:22)
  at org.scalatest.Transformer.apply(Transformer.scala:20)
  at org.scalatest.FlatSpecLike$$anon$1.apply(FlatSpecLike.scala:1639)
  ...

I found similar question (Scala Value classes and Mockito Matchers don't play together) but without any advice.

Is there any posibility to use mockito matchers with scala value class?

Lib versions: scala 2.11.2, mockito 1.10.8, scalatest 2.1.6

Community
  • 1
  • 1
Marek Kliś
  • 821
  • 1
  • 8
  • 8
  • afaik `any` and `eq` cannot be used together. – Ashalynd Dec 04 '14 at 08:57
  • @Ashalynd - from [Mockito documentation](http://mockito.github.io/mockito/docs/current/org/mockito/Mockito.html): **Warning on argument matchers:** If you are using argument matchers, all arguments have to be provided by matchers. E.g: (example shows verification but the same applies to stubbing): verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); //above is correct - eq() is also an argument matcher – Marek Kliś Dec 04 '14 at 09:07

7 Answers7

32

The proper solution is:

case class StringValue(val text: String) extends AnyVal
case class LongValue(val value: Long) extends AnyVal

val eqFirst: StringValue = StringValue(org.mockito.Matchers.eq("first"))
val anySecond: StringValue = StringValue(org.mockito.Matchers.any[String])

val eqFirst: LongValue = LongValue(org.mockito.Matchers.eq(1L))
val anySecond: LongValue = LongValue(org.mockito.Matchers.any[Long])
Marek Kliś
  • 821
  • 1
  • 8
  • 8
  • When I do this, I get a bunch of warnings and errors about not using matchers outside of stubbing: val anyProjectName = org.mockito.ArgumentMatchers.any[String].asInstanceOf[ProjectName] org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Misplaced or misused argument matcher detected here: – Gritty Kitty Aug 30 '17 at 18:51
  • 1
    I validate that `MyAnyValClass(any[String])` works without NPE in Scala 2.12.7 and Mockito 2.28 – Kaihua Jul 09 '20 at 23:36
11

I found solution:

val anyFirstId: FirstId = any[String].asInstanceOf[FirstId]
val eqSecondId: SecondId = org.mockito.Matchers.eq[String](secondId.value).asInstanceOf[SecondId]
given(mockedClass.someMethods(anyFirstId, eqSecondId)).willReturn(3)
Marek Kliś
  • 821
  • 1
  • 8
  • 8
5

This works for all kinds of extends AnyVal value classes and doesn't need special matchers either:

    given(mockedClass.someMethods(FirstId(anyString), SecondId(org.mockito.Matchers.eq(secondId.value)))).willReturn(3)
mr MR
  • 131
  • 2
  • 2
3

The newest version of mockito-scala (0.0.9) supports this out of the box, you can do something like

when(myObj.myMethod(anyVal[MyValueClass]) thenReturn "something"

myObj.myMethod(MyValueClass(456)) shouldBe "something"

verify(myObj).myMethod(eqToVal[MyValueClass](456))

Disclaimer: I'm a developer of that library

ultrasecr.eth
  • 1,437
  • 10
  • 13
  • Hi, is mockito-scala an active project? Apologies for upfront, I am asking as I don't know if it would be risky switching from mockito to mockito-scala – Manu Chadha Mar 08 '19 at 20:32
  • Yes it is, look at the commit and release history https://github.com/mockito/mockito-scala – ultrasecr.eth Mar 08 '19 at 21:55
  • Interestingly, the `any[MyValueClass]` method fails when the value class has a private constructor, other than that, the `eqTo` works as expected, thanks! – AlexITC May 02 '19 at 04:01
  • @AlexITC what scala version are you on? – ultrasecr.eth May 02 '19 at 08:03
  • The version is 2.12.2. – AlexITC May 02 '19 at 21:40
  • Yes, that's because the current macro does something like `new MyValueClass(any)`, I just improved it to check if it is also a `case class` first and in that case use the `apply` method. The next release should have it, keep tuned! – ultrasecr.eth May 05 '19 at 14:52
1

If you have shapeless in your dependencies you could consider my little helper method: https://gist.github.com/Fristi/bbc9d0e04557278f8d19976188a0b733

Instead of writing

UserId(is(context.userId.value))

You can write

isAnyVal(context.userId)

Which is a bit more convenient :-)

mark_dj
  • 984
  • 11
  • 29
0

My class also extended AnyVal

case class ApplicationKey(value: String) extends AnyVal {
  override def toString: String = value
}

and this worked for me:

   when(waterfallLogicEventWriter.writeAuctionEvents(            
     Eq(waterfallRequest.auctionId), Eq(waterfallRequest.ip),
     ApplicationKey(any[String]),                                
     any()).thenReturn(waterfallEvents) 
Rinat M.
  • 1
  • 1
0

In case there's anyone looking for a macro-based solution that works with AnyVal classes with private constructors, here's something that worked for me: https://gist.github.com/lloydmeta/e4ba5af6dae8f1c68efc6458f0a5879d

I tried doing this the "normal" way without macros, but I suspect there may be byte-code manipulation-related things happening that break normal assumptions around function substitution.

lloydmeta
  • 1,289
  • 1
  • 15
  • 25