使用 Mockito 模拟具有泛型参数的类

发布于 2024-08-09 10:00:32 字数 353 浏览 4 评论 0原文

是否有一种干净的方法来模拟带有泛型参数的类?假设我必须模拟一个 Foo 类,我需要将其传递到需要 Foo 的方法中。我可以很容易地执行以下操作:

Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());

假设 getValue() 返回泛型类型 T。但是,当我稍后将其传递到需要 Foo 的方法时,就会有小猫。铸造是做到这一点的唯一方法吗?

Is there a clean method of mocking a class with generic parameters? Say I have to mock a class Foo<T> which I need to pass into a method that expects a Foo<Bar>. I can do the following easily enough:

Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());

Assuming getValue() returns the generic type T. But that's going to have kittens when I later pass it into a method expecting Foo<Bar>. Is casting the only means of doing this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

饮湿 2024-08-16 10:00:32

我认为你确实需要投射它,但它应该不会太糟糕:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());

I think you do need to cast it, but it shouldn't be too bad:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
书信已泛黄 2024-08-16 10:00:32

另一种解决方法是使用 @Mock 注释。
并非在所有情况下都有效,但看起来更性感:)

这是一个示例:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;
    
    @Test
    public void testFoo() {
        when(fooMock.getValue()).thenReturn(new Bar());
    }
}

MockitoJUnitRunner 初始化用 @Mock

One other way around this is to use @Mock annotation instead.
Doesn't work in all cases, but looks much sexier :)

Here's an example:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;
    
    @Test
    public void testFoo() {
        when(fooMock.getValue()).thenReturn(new Bar());
    }
}

The MockitoJUnitRunner initializes the fields annotated with @Mock.

晌融 2024-08-16 10:00:32

您始终可以创建一个中间类/接口来满足您想要指定的泛型类型。例如,如果 Foo 是一个接口,您可以在测试类中创建以下接口。

private interface FooBar extends Foo<Bar>
{
}

在 Foo 是非最终类的情况下,您可以使用以下代码扩展该类并执行相同的操作:

public class FooBar extends Foo<Bar>
{
}

然后您可以使用以下代码使用上述示例之一:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());

You could always create an intermediate class/interface that would satisfy the generic type that you are wanting to specify. For example, if Foo was an interface, you could create the following interface in your test class.

private interface FooBar extends Foo<Bar>
{
}

In situations where Foo is a non-final class, you could just extend the class with the following code and do the same thing:

public class FooBar extends Foo<Bar>
{
}

Then you could consume either of the above examples with the following code:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
悸初 2024-08-16 10:00:32

创建一个测试实用方法。如果您多次需要它,则特别有用。

@Test
public void testMyTest() {
    // ...
    Foo<Bar> mockFooBar = mockFoo();
    when(mockFooBar.getValue).thenReturn(new Bar());

    Foo<Baz> mockFooBaz = mockFoo();
    when(mockFooBaz.getValue).thenReturn(new Baz());

    Foo<Qux> mockFooQux = mockFoo();
    when(mockFooQux.getValue).thenReturn(new Qux());
    // ...
}

@SuppressWarnings("unchecked") // still needed :( but just once :)
private <T> Foo<T> mockFoo() {
    return mock(Foo.class);
}

Create a test utility method. Specially useful if you need it for more than once.

@Test
public void testMyTest() {
    // ...
    Foo<Bar> mockFooBar = mockFoo();
    when(mockFooBar.getValue).thenReturn(new Bar());

    Foo<Baz> mockFooBaz = mockFoo();
    when(mockFooBaz.getValue).thenReturn(new Baz());

    Foo<Qux> mockFooQux = mockFoo();
    when(mockFooQux.getValue).thenReturn(new Qux());
    // ...
}

@SuppressWarnings("unchecked") // still needed :( but just once :)
private <T> Foo<T> mockFoo() {
    return mock(Foo.class);
}
下壹個目標 2024-08-16 10:00:32

我同意不应抑制类或方法中的警告,因为人们可能会忽略其他意外抑制的警告。但恕我直言,抑制仅影响单行代码的警告是绝对合理的。

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);

I agree that one shouldn't suppress warnings in classes or methods as one could overlook other, accidentally suppressed warnings. But IMHO it's absolutely reasonable to suppress a warning that affects only a single line of code.

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);
累赘 2024-08-16 10:00:32

对于 JUnit5,我认为最好的方法是在方法参数或字段中使用 @ExtendWith(MockitoExtension.class) 和 @Mock。

以下示例通过 Hamcrest 匹配器演示了这一点。

package com.vogella.junit5;                                                                    
                                                                                               
import static org.hamcrest.MatcherAssert.assertThat;                                           
import static org.hamcrest.Matchers.hasItem;                                                   
import static org.mockito.Mockito.verify;                                                      
                                                                                               
import java.util.Arrays;                                                                       
import java.util.List;                                                                         
                                                                                               
import org.junit.jupiter.api.Test;                                                             
import org.junit.jupiter.api.extension.ExtendWith;                                             
import org.mockito.ArgumentCaptor;                                                             
import org.mockito.Captor;                                                                     
import org.mockito.Mock;                                                                       
import org.mockito.junit.jupiter.MockitoExtension;                                             
                                                                                               
@ExtendWith(MockitoExtension.class)                                                            
public class MockitoArgumentCaptureTest {                                                      
                                                                                               
                                                                                               
    @Captor                                                                                    
    private ArgumentCaptor<List<String>> captor;                                               
                                                                                               
    @Test                                                                                      
    public final void shouldContainCertainListItem(@Mock List<String> mockedList) {            
        var asList = Arrays.asList("someElement_test", "someElement");                         
        mockedList.addAll(asList);                                                             
                                                                                               
        verify(mockedList).addAll(captor.capture());                                           
        List<String> capturedArgument = captor.getValue();                                     
        assertThat(capturedArgument, hasItem("someElement"));                                  
    }                                                                                          
}                                                                                              
                                                                                              

请参阅https://www.vogella.com/tutorials/Mockito/article.html 所需的 Maven/Gradle 依赖项。

With JUnit5 I think the best way is to @ExtendWith(MockitoExtension.class) with @Mock in the method parameter or the field.

The following example demonstrates that with Hamcrest matchers.

package com.vogella.junit5;                                                                    
                                                                                               
import static org.hamcrest.MatcherAssert.assertThat;                                           
import static org.hamcrest.Matchers.hasItem;                                                   
import static org.mockito.Mockito.verify;                                                      
                                                                                               
import java.util.Arrays;                                                                       
import java.util.List;                                                                         
                                                                                               
import org.junit.jupiter.api.Test;                                                             
import org.junit.jupiter.api.extension.ExtendWith;                                             
import org.mockito.ArgumentCaptor;                                                             
import org.mockito.Captor;                                                                     
import org.mockito.Mock;                                                                       
import org.mockito.junit.jupiter.MockitoExtension;                                             
                                                                                               
@ExtendWith(MockitoExtension.class)                                                            
public class MockitoArgumentCaptureTest {                                                      
                                                                                               
                                                                                               
    @Captor                                                                                    
    private ArgumentCaptor<List<String>> captor;                                               
                                                                                               
    @Test                                                                                      
    public final void shouldContainCertainListItem(@Mock List<String> mockedList) {            
        var asList = Arrays.asList("someElement_test", "someElement");                         
        mockedList.addAll(asList);                                                             
                                                                                               
        verify(mockedList).addAll(captor.capture());                                           
        List<String> capturedArgument = captor.getValue();                                     
        assertThat(capturedArgument, hasItem("someElement"));                                  
    }                                                                                          
}                                                                                              
                                                                                              

See https://www.vogella.com/tutorials/Mockito/article.html for the required Maven/Gradle dependencies.

挽清梦 2024-08-16 10:00:32

正如其他答案所提到的,没有一个很好的方法来使用 mock() & 。直接使用 spy() 方法,无需不安全的泛型访问和/或抑制泛型警告。

Mockito 项目中当前存在一个未解决的问题 (#1531),以添加对使用的支持mock() &没有泛型警告的 spy() 方法。该问题于 2018 年 11 月提出,但没有任何迹象表明它会被优先处理。根据 Mockito 贡献者对此问题的评论之一:

鉴于 .class 不能很好地与泛型配合,我认为我们在 Mockito 中无法提供任何解决方案。您已经可以执行 @Mock (JUnit5 扩展还允许方法参数 @Mock),这应该是一个合适的替代方案。因此,我们可以让这个问题保持开放状态,但考虑到 @Mock 是一个更好的 API,它不太可能得到解决。

As the other answers mentioned, there's not a great way to use the mock() & spy() methods directly without unsafe generics access and/or suppressing generics warnings.

There is currently an open issue in the Mockito project (#1531) to add support for using the mock() & spy() methods without generics warnings. The issue was opened in November 2018, but there aren't any indications that it will be prioritized. Per one of the Mockito contributor's comments on the issue:

Given that .class does not play well with generics, I don't think there is any solution we can do in Mockito. You can already do @Mock (the JUnit5 extension also allows method parameter @Mocks) and that should be a suitable alternative. Therefore, we can keep this issue open, but it is unlikely ever to be fixed, given that @Mock is a better API.

别挽留 2024-08-16 10:00:32

JUnit5:在测试类上使用@ExtendWith(MockitoExtension.class),然后添加此字段:

@Mock
Foo<Bar> barMock;

JUnit5: use @ExtendWith(MockitoExtension.class) on the test class then add this field:

@Mock
Foo<Bar> barMock;
天涯沦落人 2024-08-16 10:00:32

所以你有这个:

Foo mockFoo = mock(Foo.class);

修复它的方法,从我最不喜欢到最喜欢:

  1. 使用 @SuppressWarnings("unchecked") 注释。并不能真正解决问题,但您将不再收到警告。
@SuppressWarnings("unchecked")
Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
  1. 投射它。不幸的是,尽管它仍然发出警告。所以这里也需要使用注解:
@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
  1. 使用@Mock注解。不会有任何警告。这里,实际测试时可以添加when
@Mock
public Foo<Bar> fooMock;
  1. 使用@MockBean注解。这将直接创建一个模拟 bean。没有警告。
@MockBean
public Foo<Bar> fooMock;

So you have this:

Foo mockFoo = mock(Foo.class);

Ways to fix it, starting from my least favourite to most:

  1. Use @SuppressWarnings("unchecked") annotation. Doesn't really fix it, but you'll stop getting the warnings.
@SuppressWarnings("unchecked")
Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
  1. Cast it. Though it still gives warnings, unfortunately. So you need to use the annotation here as well:
@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
  1. Use @Mock annotation. There will be no warnings. Here, when can be added in actual tests.
@Mock
public Foo<Bar> fooMock;
  1. Use @MockBean annotation. This will create a mocked bean directly. No warnings.
@MockBean
public Foo<Bar> fooMock;
我是男神闪亮亮 2024-08-16 10:00:32

这是一个有趣的案例:方法接收泛型集合并返回相同基类型的泛型集合。例如:

Collection<? extends Assertion> map(Collection<? extends Assertion> assertions);

可以使用 Mockito anyCollectionOf 匹配器和答案的组合来模拟此方法。

when(mockedObject.map(anyCollectionOf(Assertion.class))).thenAnswer(
     new Answer<Collection<Assertion>>() {
         @Override
         public Collection<Assertion> answer(InvocationOnMock invocation) throws Throwable {
             return new ArrayList<Assertion>();
         }
     });

Here is an interesting case: method receieves generic collection and returns generic collection of same base type. For example:

Collection<? extends Assertion> map(Collection<? extends Assertion> assertions);

This method can be mocked with combination of Mockito anyCollectionOf matcher and the Answer.

when(mockedObject.map(anyCollectionOf(Assertion.class))).thenAnswer(
     new Answer<Collection<Assertion>>() {
         @Override
         public Collection<Assertion> answer(InvocationOnMock invocation) throws Throwable {
             return new ArrayList<Assertion>();
         }
     });
别忘他 2024-08-16 10:00:32

(在我看来)最简单、最易读的方法是使用方法级注入。

这将导致测试方法中包含所有测试数据。这将使您的测试类保持干净,因为没有“浮动”模拟。

@ExtendWith(MockitoExtension.class)
public class SomeClassTest {

    @Test
    void someTestMethod(@Mock Foo<Bar> fooMock) {
        // do something with your mock
    }
    
}

The (in my opinion) most easiest and most readable approach is to use method level injection.

This will result in having all test data within the test method. This will keep your test classes clean as there are no 'floating' mock's.

@ExtendWith(MockitoExtension.class)
public class SomeClassTest {

    @Test
    void someTestMethod(@Mock Foo<Bar> fooMock) {
        // do something with your mock
    }
    
}
踏雪无痕 2024-08-16 10:00:32

为什么不使用间谍

var mock = spy(new Foo<Bar>());
when(mockFoo.getValue()).thenReturn(new Bar());

why not using spy

var mock = spy(new Foo<Bar>());
when(mockFoo.getValue()).thenReturn(new Bar());
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文