I'm working on an Android app, using AndroidStudio and am hoping someone could tell me why I can't get Mockito to recognize arguments using argumentCaptor.capture()
or anyObject()
.
I'm testing SpanPainter
's method 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;
}
}
My test is:
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());
}
}
The results are: (I removed the angle brackets)
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
);
If I remove the commented line and use anyObject(), the results are:
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
);
It seems to me that at least using anyObject()
should work, but it doesn't.
Following code gives similar results, 'Arguments are different!Wanted...' 'Capturing argument' vs 'Mock for ForegroundColorSpan, hashCode: xxxx':
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());
}
}
Here are my gradle settings:
From overall build.gradle file
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
}
}
allprojects {
repositories {
jcenter()
}
}
From app build.gradle file:
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'
}
I also tried the following, using 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);
which continues as before; this also doesn't solve the problem.
This test uses returnDefaultValues = true
, does not use Powermockito, and uses Object
instead of ForegroundColorSpan
. Testing to see if subclassing android method and implementing a non-android interface would work.
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());
}
}
Interface:
public interface SpannableStringSuper {
public void setSpan(Object what, int start, int end, int flags);
}
Subclass:
public class SpannableStringSubclass extends SpannableString implements SpannableStringSuper {
public SpannableStringSubclass () {
super("xxx");
}
}
This results in very similar results as first test. 'any' vs 'Mock for Object'. And 'Capturing argument' vs 'Mock for Object'.
I can't reproduce this. Here's the code I used to try to match your scenario:
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());
}
}
This passes just fine for me. I'm using:
Clearly, the fact that mine works and yours doesn't means there's some issue with something outside the class; it could either be a dependency issue or a mock settings configuration issue.
Is it possible you're somehow using two different versions of ForegroundColorSpan
? Are you setting Mockito settings outside of the tests you've shown me somewhere?
You could try would be swapping them both out separately and seeing if they work. For example, try this:
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);
}
}
Then, try something similar using a SpannableString
type and a different Object
where you're currently using ForegroundColorSpan
.
I tried running DummyTest
again, with RETURNS_DEFAULTS
based on your answer - but doing it inside the test class and not in the build settings, like so:
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());
}
}
This also works and doesn't throw your error. Therefore it has something to do with the gradle build setting itself, and not RETURNS_DEFAULTS
on it's face.
I think that having unitTest.returnDefaultValues = true
in the project gradle.build file results in default mock objects instead of plain mock Android objects. I think that Mockito does not allow these default mock objects to be used as true mock objects. I think this because method verification on these default mock objects never passed in my tests.
defaultObject = mock(SomeAndroidClass.class)
verify(defaultObject).method(argumentCaptor.capture())
The above will not pass. The arguments never matched.
I know this answer is not completely substantiated by the documentation, but it is my understanding as of now. I base this on the tests I ran, see question text and also from this link in the "Method... not mocked" section.
It does seem that mock Android objects can be used as ArgumentCaptor
types. These act more like "doubles", in that method verification is not performed on these default mock objects. I think this is true because @durron597's DummyTest
with the TestSpannable
interface passed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With