为什么ArgumentCaptor没有正确匹配?

时间:2015-08-17 16:05:12

标签: java android mockito

我使用AndroidStudio在Android应用上工作,我希望有人可以告诉我为什么我不能让Mockito使用argumentCaptor.capture()anyObject()识别参数。

我正在测试SpanPainter的方法applyColor()

package com.olfybsppa.inglesaventurero.utils;

import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;

public class SpanPainter {
  ForegroundColorSpan color;

  public SpanPainter (ForegroundColorSpan color) {
    this.color = color;
  }
  public SpannableString applyColor(SpannableString span) {
    span.setSpan(color, 1, 2, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    return span;
  }
}

我的测试是:

package com.olfybsppa.inglesaventurero.utils;

import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

public class SpanPainterTest {
  @Test
  public void testAppliesColorPerRange () {
    SpannableString mockSpanString = mock(SpannableString.class);
    ForegroundColorSpan mockForegroundColor = mock(ForegroundColorSpan.class);
    SpanPainter painter = new SpanPainter(mockForegroundColor);

    ArgumentCaptor<ForegroundColorSpan> argumentCaptor = ArgumentCaptor.forClass(ForegroundColorSpan.class);
    painter.applyColor(mockSpanString);

    verify(mockSpanString).setSpan(argumentCaptor.capture(), anyInt(), anyInt(), anyInt());
    //verify(mockSpanString).setSpan(anyObject(), anyInt(), anyInt(), anyInt());
  }
}

结果是:(我删除了尖括号)

Argument(s) are different! Wanted:
spannableString.setSpan(
    Capturing argument,
    any,
    any,
    any
);
Actual invocation has different arguments:
spannableString.setSpan(
    Mock for ForegroundColorSpan, hashCode: 106298691,
    1,
    2,
    17
);

如果我删除注释行并使用anyObject(),结果为:

Argument(s) are different! Wanted:
spannableString.setSpan(any,any,any,any);
Actual invocation has different arguments:
spannableString.setSpan(
    Mock for ForegroundColorSpan, hashCode: 106298691,
    1,
    2,
    17
);

在我看来,至少使用anyObject()应该有效,但事实并非如此。

不再引用我的主代码,仍使用Android api对象

以下代码给出了类似的结果,&#39;参数不同!通缉......&#39; &#39;捕捉论点&#39; vs&#39;模拟ForegroundColorSpan,hashCode:xxxx&#39;:

package com.olfybsppa.inglesaventurero.utils;

import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

public class DummyTest {
  @Test
  public void testCaptor() {
    SpannableString helper = mock(SpannableString.class);
    ForegroundColorSpan color = mock(ForegroundColorSpan.class);
    helper.setSpan(color, 1, 2, 17);
    ArgumentCaptor<ForegroundColorSpan> captor = ArgumentCaptor.forClass(ForegroundColorSpan.class);
    verify(helper).setSpan(captor.capture(), anyInt(), anyInt(), anyInt());
  }

}

Gradle设置:

以下是我的gradle设置:

来自整体build.gradle文件

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:1.3.0'
  }
}

allprojects {
  repositories {
    jcenter()
  }
}

来自app build.gradle文件:

apply plugin: 'com.android.application'

android {
  compileSdkVersion 20
  buildToolsVersion "20.0.0"

  defaultConfig {
    applicationId "com.olfybsppa.inglesadventurero"
    minSdkVersion 15
    targetSdkVersion 15
    versionCode 5
    versionName "5.0"
  }
  buildTypes {
    release {
      minifyEnabled true
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }
  sourceSets {
    main {
    java.srcDirs = ['src/main/java', 'src/main/java/start', 'src/main/java/adapters', 'src/main/java/pagers', 'src/main/java/com.olfybsppa.inglesadventurero/pagers']
    }
  }
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_7
    targetCompatibility JavaVersion.VERSION_1_7
  }
  testOptions {
    unitTests.returnDefaultValues = true
  }
}

dependencies {
  compile 'com.android.support:support-v4:20.0.+'
  compile fileTree(dir: 'libs', include: ['*.jar'])
  compile 'com.google.android.gms:play-services-ads:6.+'
  testCompile 'junit:junit:4.12'
  testCompile 'org.mockito:mockito-core:1.10.19'
}

PowerMockito也不起作用:

我还使用PowerMockito尝试了以下内容:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SpannableString.class, ForegroundColorSpan.class })
public class DummyTest {
  @Test
  public void testCaptor() {
    SpannableString helper = PowerMockito.mock(SpannableString.class);
    ForegroundColorSpan color = PowerMockito.mock(ForegroundColorSpan.class);

继续像以前一样;这也没有解决问题。

添加非android界面:

此测试使用returnDefaultValues = true,不使用Powermockito,并使用Object代替ForegroundColorSpan。测试是否可以使用子类化android方法并实现非android界面。

public class SpanPainterTest {
  @Test
  public void testCaptor() {
    SpannableStringSubclass helper = mock(SpannableStringSubclass.class);
    Object color = mock(Object.class);
    helper.setSpan(color, 1, 2, 17);
    ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
    verify(helper).setSpan(anyObject(), anyInt(), anyInt(), anyInt());
    //verify(helper).setSpan(captor.capture(), anyInt(), anyInt(), anyInt());
  }
}

接口:

public interface SpannableStringSuper {
  public void setSpan(Object what, int start, int end, int flags);
}

子类:

public class SpannableStringSubclass extends SpannableString implements SpannableStringSuper {
  public SpannableStringSubclass () {
    super("xxx");
  }
}

这导致与第一次测试非常相似的结果。 &#39;任何&#39; vs&#39;模拟对象&#39;。并且&#39;捕捉论证&#39; vs&#39; Mock for Object&#39;。

2 个答案:

答案 0 :(得分:4)

我无法重现这一点。这是我用来尝试匹配您的场景的代码:

public class DummyTest {
  @Test
  public void testCaptor() {
    SpannableString helper = mock(SpannableString.class);
    ForegroundColorSpan color = mock(ForegroundColorSpan.class);
    helper.setSpan(color, 1, 2, 17);
    ArgumentCaptor<ForegroundColorSpan> captor = ArgumentCaptor.forClass(ForegroundColorSpan.class);
    verify(helper).setSpan(captor.capture(), anyInt(), anyInt(), anyInt());
  }
}

这对我来说很好。我正在使用:

  • 的Maven
  • Android 4.1.1.4
  • JUnit 4.11
  • Mockito 1.10.19

显然,我的工作和你的工作并不意味着课外的某些问题;它可能是依赖问题或模拟设置配置问题。

您是否有可能以某种方式使用两个不同版本的ForegroundColorSpan?你是否在你向我展示的测试之外设置了Mockito设置?

您可以尝试将它们交换出单独并查看它们是否有效。例如,试试这个:

public class DummyTest {
  @Test
  public void testCaptor() {
    TestSpannable helper = mock(TestSpannable.class);
    ForegroundColorSpan color = mock(ForegroundColorSpan.class);
    helper.setSpan(color, 1, 2, 17);
    ArgumentCaptor<ForegroundColorSpan> captor = ArgumentCaptor.forClass(ForegroundColorSpan.class);
    verify(helper).setSpan(captor.capture(), anyInt(), anyInt(), anyInt());
  }

  public static interface TestSpannable {
    public void setString(Object what, int start, int end, int flags);
  }
}

然后,尝试使用SpannableString类型和您当前使用Object的其他ForegroundColorSpan类似的内容。

我尝试再次使用基于your answerDummyTest运行RETURNS_DEFAULTS - 但是在测试类中而不是在构建设置中执行此操作,如下所示:

public class DummyTest {
  @Test
  public void testCaptor() {
    SpannableString helper = mock(SpannableString.class, RETURNS_DEFAULTS);
    ForegroundColorSpan color = mock(ForegroundColorSpan.class, RETURNS_DEFAULTS);
    helper.setSpan(color, 1, 2, 17);
    ArgumentCaptor<ForegroundColorSpan> captor = ArgumentCaptor.forClass(ForegroundColorSpan.class);
    verify(helper).setSpan(captor.capture(), anyInt(), anyInt(), anyInt());
  }
}

这个有效并且不会抛出你的错误。因此它与gradle构建设置本身有关,而不是RETURNS_DEFAULTS在它的脸上。

答案 1 :(得分:1)

我认为在项目gradle.build文件中使用unitTest.returnDefaultValues = true会导致默认的模拟对象而不是普通的模拟Android对象。我认为Mockito不允许将这些默认模拟对象用作真正的模拟对象。我认为这是因为这些默认模拟对象的方法验证从未在我的测试中传递过。

defaultObject = mock(SomeAndroidClass.class)
verify(defaultObject).method(argumentCaptor.capture()) 

以上不会通过。论点从未匹配过。

我知道这个答案并没有完全通过文档证实,但这是我现在的理解。我根据我运行的测试,查看问题文本以及{&#34;方法中的this link ...而不是模拟&#34;部分。

似乎模拟Android对象可以用作ArgumentCaptor类型。这些更像是&#34;双打&#34;,因为该方法不对这些默认模拟对象执行验证。我认为这是真的,因为@durron597's DummyTest with the TestSpannable interface passed.