2016-04-24 20 views
1

In unten Code funktioniert Mockito Verify nicht wie erwartet auf Scala-Methoden mit Standardparameter, aber funktioniert gut bei Methoden ohne Standardparameter.Mockito Verify schlägt mit "TooManyActualInvocations" für Methode mit einem Standardparameter in Scala fehl

package verifyMethods 

import org.junit.runner.RunWith 
import org.mockito.Mockito 
import org.mockito.Mockito.times 
import org.scalatest.FlatSpec 
import org.scalatest.Matchers.be 
import org.scalatest.Matchers.convertToAnyShouldWrapper 
import org.scalatest.junit.JUnitRunner 
import org.scalatest.mock.MockitoSugar 

trait SUT { 

    def someMethod(bool: Boolean): Int = if (bool) 4 else 5 

    def someMethodWithDefaultParameter(bool: Boolean, i: Int = 5): Int = if (bool) 4 else i 
} 

@RunWith(classOf[JUnitRunner]) 
class VerifyMethodWithDefaultParameter extends FlatSpec with MockitoSugar with SUT { 

    "mockito verify method" should "pass" in { 
    val sutMock = mock[SUT] 
    Mockito.when(sutMock.someMethod(true)).thenReturn(4, 6) 

    val result1 = sutMock.someMethod(true) 
    result1 should be(4) 

    val result2 = sutMock.someMethod(true) 
    result2 should be(6) 

    Mockito.verify(sutMock, times(2)).someMethod(true) 
    } 
    //this test fails with assertion error 
    "mockito verify method with default parameter" should "pass" in { 
    val sutMock = mock[SUT] 
    Mockito.when(sutMock.someMethodWithDefaultParameter(true)).thenReturn(4, 6) 

    val result1 = sutMock.someMethodWithDefaultParameter(true) 
    result1 should be(4) 

    val result2 = sutMock.someMethodWithDefaultParameter(true) 
    result2 should be(6) 

    Mockito.verify(sutMock, times(2)).someMethodWithDefaultParameter(true) 
    } 
} 

Bitte schlagen Sie vor, was ich im zweiten Test falsch mache.


Edit 1: @Som Hier finden Sie Stacktrace für oben Test-Klasse: -

Run starting. Expected test count is: 2 
VerifyMethodWithDefaultParameter: 
mockito verify method 
- should pass 
mockito verify method with default parameter 
- should pass *** FAILED *** 
    org.mockito.exceptions.verification.TooManyActualInvocations: sUT.someMethodWithDefaultParameter$default$2(); 
Wanted 2 times: 
-> at zeither.VerifyMethodWithDefaultParameter$$anonfun$2.apply$mcV$sp(VerifyMethodWithDefaultParameter.scala:37) 
But was 3 times. Undesired invocation: 
-> at zeither.VerifyMethodWithDefaultParameter$$anonfun$2.apply$mcV$sp(VerifyMethodWithDefaultParameter.scala:34) 
    ... 
Run completed in 414 milliseconds. 
Total number of tests run: 2 
Suites: completed 1, aborted 0 
Tests: succeeded 1, failed 1, canceled 0, ignored 0, pending 0 
*** 1 TEST FAILED *** 

Edit 2: @Mifeet

Wie bereits angedeutet, wenn Ich übergebe 0 für Standard-int-Parametertest-Pässe, aber unter Testfall wird nicht mit dem vorgeschlagenen Aprr übergeben oach: -

"mockito verify method with default parameter" should "pass" in { 
    val sutMock = mock[SUT] 
    Mockito.when(sutMock.someMethodWithDefaultParameter(true, 0)).thenReturn(14) 
    Mockito.when(sutMock.someMethodWithDefaultParameter(false, 0)).thenReturn(16) 
    val result1 = sutMock.someMethodWithDefaultParameter(true) 
    result1 should be(14) 

    val result2 = sutMock.someMethodWithDefaultParameter(false) 
    result2 should be(16) 

    Mockito.verify(sutMock, times(1)).someMethodWithDefaultParameter(true) 
    Mockito.verify(sutMock, times(1)).someMethodWithDefaultParameter(false) 
    } 

Hier finden Sie Stacktrace: -

mockito verify method with default parameter 
- should pass *** FAILED *** 
    org.mockito.exceptions.verification.TooManyActualInvocations: sUT.someMethodWithDefaultParameter$default$2(); 
Wanted 1 time: 
-> at zeither.VerifyMethodWithDefaultParameter$$anonfun$2.apply$mcV$sp(VerifyMethodWithDefaultParameter.scala:38) 
But was 2 times. Undesired invocation: 
-> at zeither.VerifyMethodWithDefaultParameter$$anonfun$2.apply$mcV$sp(VerifyMethodWithDefaultParameter.scala:35) 
    ... 

Ihre Meinung zu anderen bestehenden spöttischen Bibliotheken wie PowerMock, ScalaMock wird sehr geschätzt, wenn sie eine saubere Lösung für solche Fälle zur Verfügung stellen kann, wie Ich bin offen, jede spöttische Bibliothek in meinem Projekt zu verwenden.

+0

Bitte klar, was Sie „erwarten“ und was Sie sehen. –

+0

Siehe meine aktualisierte Antwort. Manchmal müssen Sie sich nur die Hände schmutzig machen, bevor Sie sich in Biegerahmen für etwas ertränken, für das sie nicht bestimmt waren. – Mifeet

Antwort

1

Der Kürze halber verwende ich withDefaultParam() anstelle von someMethodWithDefaultParameter().

Wie Standardparameter Bytecode übersetzt werden: Um zu verstehen, warum der Test fehlschlägt, müssen wir zunächst prüfen, wie Methoden mit Standardparametern übersetzt werden Java-Äquivalent/Bytecode. Ihre Methode withDefaultParam() wird auf zwei Methoden übersetzt werden:

  • withDefaultParam - diese Methode beiden Parameter akzeptiert und tut enthält die tatsächliche Umsetzung
  • withDefaultParam$default$2 - gibt den Standardwert des zweiten Parameters (dh i)

Wenn Sie z. B. withDefaultParam(true) aufrufen, wird in den Aufruf withDefaultParam$default$2 übersetzt, um den Standardparameterwert zu erhalten, gefolgt von einem Aufruf von withDefaultParam. Sie können den Bytecode unten auschecken.

Was ist falsch mit Ihrem Test: Worüber sich Mockito beschwert, ist ein zusätzlicher Aufruf von withDefaultParam$default$2. Dies liegt daran, dass der Compiler einen zusätzlichen Aufruf für diese Methode direkt vor Ihrem Mockito.when(...) einfügt, um den Standardwert auszufüllen. Daher wird diese Methode dreimal aufgerufen und times(2) Assertion schlägt fehl.

Wie es zu beheben: Ihr Test passieren, wenn Sie Ihren Mock mit initialisieren:

Mockito.when(sutMock.withDefaultParam(true, 0)).thenReturn(4, 6) 

Das ist seltsam, könnten Sie fragen, warum ich 0 für die Standard-Parameter statt 5 passieren sollte? Es stellt sich heraus, dass Mockito die Methode withDefaultParam$default$2 auch mit der Standardeinstellung Answers.RETURNS_DEFAULTS verhöhnt. Da 0 der Standardwert für int, ist, übergeben alle Aufrufe in Ihrem Code 0 statt 5 als das zweite Argument zu withDefaultParam().

Wie die richtige Standardwert für den Parameter erzwingen: Wenn Sie Ihren Test wollte 5 als Standardwert zu verwenden, können Sie Ihren Test mit so etwas wie dies passieren machen könnte:

class SUTImpl extends SUT 
val sutMock = mock[SUTImpl](Mockito.CALLS_REAL_METHODS) 
Mockito.when(sutMock.withDefaultParam(true, 5)).thenReturn(4, 6) 

Meiner Meinung Aber genau hier hört Mockito auf, nützlich zu sein und wird zur Last. Was wir in unserem Team tun würden, ist eine benutzerdefinierte Test-Implementierung von SUT ohne Mockito zu schreiben. Es verursacht keine überraschenden Fallstricke wie oben, Sie können benutzerdefinierte Assertionslogik implementieren und, am wichtigsten, es kann zwischen den Tests wiederverwendet werden.

Update - wie ich es lösen würde: Ich denke nicht, dass eine spöttische Bibliothek wirklich einen Vorteil in diesem Fall bietet. Es ist weniger Schmerz, deinen eigenen Spott zu programmieren. Dies ist, wie ich es gehen würde:

class SUTMock(results: Map[Boolean, Seq[Int]]) extends SUT { 
    private val remainingResults = results.mapValues(_.iterator).view.force // see http://stackoverflow.com/a/14883167 for why we need .view.force 

    override def someMethodWithDefaultParameter(bool: Boolean, i: Int): Int = remainingResults(bool).next() 

    def assertNoRemainingInvocations() = remainingResults.foreach { 
    case (bool, remaining) => assert(remaining.isEmpty, s"remaining invocations for parameter $bool: ${remaining.toTraversable}") 
    } 
} 

Ein Test wird dann wie folgt aussehen könnte:

"mockito verify method with default parameter" should "pass" in { 
    val sutMock = new SUTMock(Map(true -> Seq(14, 15), false -> Seq(16))) 
    sutMock.someMethodWithDefaultParameter(true) should be(14) 
    sutMock.someMethodWithDefaultParameter(true) should be(15) 

    sutMock.someMethodWithDefaultParameter(false) should be(16) 

    sutMock.assertNoRemainingInvocations() 
    } 

Dies tut alles, was Sie brauchen - liefert, die erforderlich Rückgabewerte, bläst auf zu viele oder zu wenige Aufrufe. Es kann wiederverwendet werden. Dies ist ein dummes, vereinfachtes Beispiel, aber in einem praktischen Szenario sollten Sie eher über Ihre Geschäftslogik als über Methodenaufrufe nachdenken. Wenn SUT ein Mock für den Nachrichtenbroker war, könnten Sie beispielsweise eine Methode allMessagesProcessed() anstelle von assertNoRemainingInvocations() haben oder sogar komplexere Behauptungen definieren.


Angenommen wir eine Variable val sut:SUT haben, hier ist der Bytecode withDefaultParam(true) des Aufrufs:

ALOAD 1 # load sut on stack 
ICONST_1 # load true on stack 
ALOAD 1 # load sut on stack 
INVOKEINTERFACE SUT.withDefaultParam$default$2()I # call method which returns the value of the default parameter and leave result on stack 
INVOKEINTERFACE SUT.withDefaultParam (ZI)I   # call the actual implementation 
Verwandte Themen