Mockito matchers, scala value class and NullPointerException
Asked Answered
U

7

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

Ungenerous answered 4/12, 2014 at 8:53 Comment(2)
afaik any and eq cannot be used together.Crandell
@Crandell - from Mockito documentation: 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 matcherFledgy
U
34

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])
Ungenerous answered 21/1, 2016 at 20:58 Comment(2)
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:Candent
I validate that MyAnyValClass(any[String]) works without NPE in Scala 2.12.7 and Mockito 2.28Tami
U
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)
Ungenerous answered 4/12, 2014 at 10:54 Comment(7)
It would be cool if you implement your own matcher for AnyVals and put it on GitHub.Ilianailine
It's not easy to write generic eq matcher for AnyVal.Fledgy
Has this worked for you? I get a ClassCastException.Reinhart
Ah, I see, it seems to work for String value classes but not for any primitive value classes: pastebin.com/pUF3muEFReinhart
@cristian-vrabie So what is solution for value classes with primitives such as Int?Captor
Thanks for that useful answer. Could you explain why this works, unlike the "traditional" way ?Shall
I validate that any[String].asInstanceOf[FirstId] works in Scala 2.12.7 and Mockito 2.28.Tami
Z
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)
Zapata answered 29/8, 2016 at 14:35 Comment(0)
A
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

Astronomer answered 2/8, 2018 at 7:4 Comment(6)
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-scalaTerri
Yes it is, look at the commit and release history github.com/mockito/mockito-scalaAstronomer
Interestingly, the any[MyValueClass] method fails when the value class has a private constructor, other than that, the eqTo works as expected, thanks!Karl
@Karl what scala version are you on?Astronomer
The version is 2.12.2.Karl
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!Astronomer
N
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 :-)

Ninepins answered 4/7, 2017 at 9:22 Comment(0)
O
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) 
Officiant answered 3/11, 2020 at 7:54 Comment(0)
J
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.

Josuejosy answered 27/4, 2021 at 7:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.