如何使用Mockito捕获特定类型的列表


301

有没有一种方法可以使用嘲笑ArgumentCaptore捕获特定类型的列表。这不起作用:

ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);

8
我发现在这里(ArrayList)使用具体列表实现是一个糟糕的主意。您可以始终使用List接口,如果要表示事实,它是协变的,则可以使用extendsArgumentCaptor<? extends List<SomeType>>
tenshi 2013年

Answers:


533

可以使用@Captor注释避免嵌套的泛型问题:

public class Test{

    @Mock
    private Service service;

    @Captor
    private ArgumentCaptor<ArrayList<SomeType>> captor;

    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }

    @Test 
    public void shouldDoStuffWithListValues() {
        //...
        verify(service).doStuff(captor.capture()));
    }
}

70
我更喜欢MockitoAnnotations.initMocks(this)@Before方法中使用,而不是使用不包含使用其他运行器的功能的运行器。但是,+ 1,感谢您指出注释。
约翰·B

4
不确定此示例是否完整。我得到...错误:(240,40)java:变量捕捉器可能尚未初始化,我喜欢下面的tenshi的答案
Michael Dausmann 2014年

1
我遇到了同样的问题,发现这篇博客文章对我有所帮助:blog.jdriven.com/2012/10/…。将注释放在您的类上后,它包括使用MockitoAnnotations.initMocks的步骤。我注意到的一件事是您不能在局部变量中包含它。
SlopeOak 2015年

1
@ chamzz.dot ArgumentCaptor <ArrayList <SomeType >>捕获器; 已经捕获了“ SomeType” <-是特定类型的数组,不是吗?
Miguel R. Santaella '18年

1
我通常在Captor声明中更喜欢使用List而不是ArrayList:ArgumentCaptor <List <SomeType >> captor;
Miguel R. Santaella '18年

146

是的,这是一个通用的泛型问题,而不是特定于Mockito的问题。

没有用于的类对象ArrayList<SomeType>,因此您不能将此类对象类型安全地传递给需要的方法Class<ArrayList<SomeType>>

您可以将对象转换为正确的类型:

Class<ArrayList<SomeType>> listClass =
              (Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);

这将发出有关不安全强制转换的警告,当然ArrayList<SomeType>ArrayList<AnotherType>如果不检查元素,则ArgumentCaptor不能真正区分。

(如另一个答案中所述,尽管这是一个通用的泛型问题,但是对于带有@Captor注释的类型安全性问题,有一个Mockito特定的解决方案。它仍然无法区分an ArrayList<SomeType>和an ArrayList<OtherType>。)

编辑:

还要看看tenshi的评论。您可以将原始代码从PaŭloEbermann更改为此(更简单)

final ArgumentCaptor<List<SomeType>> listCaptor
        = ArgumentCaptor.forClass((Class) List.class);

49
基于Java为静态方法调用进行类型推断的事实,可以简化您显示的示例:ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
tenshi 2013年

4
要禁用使用未经检查或不安全的操作警告,请使用@SuppressWarnings("unchecked")参数捕获器定义行上方的注释。同样,强制转换Class为多余的。
mrts

1
Class在我的测试中,强制转换为不是多余的。
Wim Deblauwe,

16

如果您不担心旧的Java样式(非类型安全的泛型)语义,这也可以工作并且相当简单:

ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(subject.method(argument.capture()); // run your code
List<SomeType> list = argument.getValue(); // first captured List, etc.

2
您可以在声明之前添加@SuppressWarnings(“ rawtypes”)以禁用警告。
pkalinow

9
List<String> mockedList = mock(List.class);

List<String> l = new ArrayList();
l.add("someElement");

mockedList.addAll(l);

ArgumentCaptor<List> argumentCaptor = ArgumentCaptor.forClass(List.class);

verify(mockedList).addAll(argumentCaptor.capture());

List<String> capturedArgument = argumentCaptor.<List<String>>getValue();

assertThat(capturedArgument, hasItem("someElement"));

4

基于@tenshi和@pkalinow的注释(也对@rogerdpack表示敬意),以下是创建列表参数捕获器的简单解决方案,该捕获器还禁用了“使用未经检查或不安全的操作”警告:

@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
    ArgumentCaptor.forClass(List.class);

完整的示例在这里,相应的合格CI构建和测试在这里运行。

我们的团队已经在单元测试中使用了一段时间,这对于我们来说似乎是最直接的解决方案。


2

对于早期版本的junit,您可以执行

Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);

1

我的Android应用程序中的测试活动存在相同的问题。我用ActivityInstrumentationTestCase2MockitoAnnotations.initMocks(this);没有工作。我用分别与领域的另一个类解决了这个问题。例如:

class CaptorHolder {

        @Captor
        ArgumentCaptor<Callback<AuthResponse>> captor;

        public CaptorHolder() {
            MockitoAnnotations.initMocks(this);
        }
    }

然后,在活动测试方法中:

HubstaffService hubstaffService = mock(HubstaffService.class);
fragment.setHubstaffService(hubstaffService);

CaptorHolder captorHolder = new CaptorHolder();
ArgumentCaptor<Callback<AuthResponse>> captor = captorHolder.captor;

onView(withId(R.id.signInBtn))
        .perform(click());

verify(hubstaffService).authorize(anyString(), anyString(), captor.capture());
Callback<AuthResponse> callback = captor.getValue();

0

Mockito的GitHub中有一个关于此确切问题的公开问题。

我发现了一个简单的解决方法,它不会强迫您在测试中使用批注:

import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;

public final class MockitoCaptorExtensions {

    public static <T> ArgumentCaptor<T> captorFor(final CaptorTypeReference<T> argumentTypeReference) {
        return new CaptorContainer<T>().captor;
    }

    public static <T> ArgumentCaptor<T> captorFor(final Class<T> argumentClass) {
        return ArgumentCaptor.forClass(argumentClass);
    }

    public interface CaptorTypeReference<T> {

        static <T> CaptorTypeReference<T> genericType() {
            return new CaptorTypeReference<T>() {
            };
        }

        default T nullOfGenericType() {
            return null;
        }

    }

    private static final class CaptorContainer<T> {

        @Captor
        private ArgumentCaptor<T> captor;

        private CaptorContainer() {
            MockitoAnnotations.initMocks(this);
        }

    }

}

这里发生的是,我们创建一个新类具有@Captor注释和注入俘虏了进去。然后,我们只需提取captor并从我们的静态方法中将其返回。

在测试中,您可以像这样使用它:

ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());

或使用类似于Jackson的语法TypeReference

ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
    new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
    }
);

之所以有效,是因为Mockito实际上不需要任何类型信息(例如,不同于序列化程序)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.