如何模拟void方法抛出异常? [英] How can I mock a void method to throw an exception?

查看:1422
本文介绍了如何模拟void方法抛出异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这样的结构:

public class CacheWrapper {
    private Map<Object, Object> innerMap;

    public CacheWrapper() {
        //initialize the innerMap with an instance for an in-memory cache
        //that works on external server
        //current implementation is not relevant for the problem
        innerMap = ...;
    }

    public void putInSharedMemory(Object key, Object value) {
        innerMap.put(key, value);
    }

    public Object getFromSharedMemory(Object key) {
        return innerMap.get(key);
    }
}

我的客户端类(你可以说它看起来像这个):

And my client class (you could say it looks like this):

public class SomeClient {
    //Logger here, for exception handling
    Logger log = ...;
    private CacheWrapper cacheWrapper;
    //getter and setter for cacheWrapper...

    public Entity getEntity(String param) {
        Entity someEntity = null;
        try {
            try {
                entity = cacheWrapper.getFromSharedMemory(param);
            } catch (Exception e) {
                //probably connection failure occurred here
                log.warn("There was a problem when getting from in-memory " + param + " key.", e);
            }
            if (entity == null) {
                entity = ...; //retrieve it from database
                //store in in-memory cache
                try {
                    cacheWrapper.put(param, entity);
                } catch (Exception e) {
                    //probably connection failure occurred here
                    log.warn("There was a problem when putting in in-memory " + param + " key.", e);
                }
            }
        } catch (Exception e) {
            logger.error(".......", e);
        }
        return entity;
    }
}

我正在为<创建单元测试code> SomeClient#getEntity 方法,必须涵盖所有方案。例如,我需要介绍 cacheWrapper 引发异常的情况。我正在遵循的方法是为 CacheWrapper 类创建一个模拟,使 CacheWrapper 类上的方法抛出一个 RuntimeException ,在 SomeClient 的实例中设置此模拟并测试 Someclient #getEntity 。问题在于尝试模拟 putInSharedMemory 方法,因为 void 。我已经尝试了很多方法来做到这一点,但没有一个工作。 该项目依赖于PowerMock和EasyMock

I'm creating unit tests for SomeClient#getEntity method and have to cover all scenarios. For instance, I need to cover the scenario where there are exceptions thrown by cacheWrapper. The approach I'm following is to create a mock for CacheWrapper class, make the methods on CacheWrapper class to throw a RuntimeException, set this mock in an instance of SomeClient and test Someclient#getEntity. The problem is when trying to mock putInSharedMemory method because is void. I have tried lot of ways to do this but none of them work. The project has dependencies for PowerMock and EasyMock.

以下是我的尝试:


  1. 使用 EasyMock。< Void>期望。这引发了编译错误。

  2. 尝试存根 CacheWrapper#putInSharedMemory 。没有用,因为这个错误消息引发了异常:

  1. Using EasyMock.<Void>expect. This raised a compiler error.
  2. Tried to stub CacheWrapper#putInSharedMemory. Didn't worked because raised an exception with this error message:


java.lang.AssertionError:意外的方法调用putInSharedMemory(foo, com.company.domain.Entity@609fc98)

java.lang.AssertionError: Unexpected method call putInSharedMemory("foo", com.company.domain.Entity@609fc98)


  • 为项目增加了Mockito依赖利用 PowerMockito 类的功能。但这引发了一个例外,因为它没有与EasyMock集成。这是引发的异常:

  • Added Mockito dependency to the project to make use of the functionality of PowerMockito class. But this raised an exception because it doesn't integrate with EasyMock. This is the exception raised:


    java.lang.ClassCastException:org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl无法强制转换to org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl

    java.lang.ClassCastException: org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl cannot be cast to org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl


  • 这里是此单元测试示例的代码:

    Here's the code for this unit test sample:

    @Test
    public void getEntityWithCacheWrapperException() {
        CacheWrapper cacheWrapper = mockThrowsException();
        SomeClient someClient = new SomeClient();
        someClient.setCacheWrapper(cacheWrapper);
        Entity entity = someClient.getEntity();
        //here.....................^
        //cacheWrapper.putInSharedMemory should throw an exception
    
        //start asserting here...
    }
    
    //...
    
    public CacheWrapper mockThrowsException() {
        CacheWrapper cacheWrapper = PowerMock.createMock(CacheWrapper.class);
        //mocking getFromSharedMemory method
        //this works like a charm
        EasyMock.expect(cacheWrapper.getFromSharedMemory(EasyMock.anyObject()))
            .andThrow(new RuntimeException("This is an intentional Exception")).anyTimes();
    
        //mocking putInSharedMemory method
        //the pieces of code here were not executed at the same time
        //instead they were commented and choose one approach after another
    
        //attempt 1: compiler exception: <Void> is not applicable for <void>
        EasyMock.<Void>expect(cacheWrapper.putInSharedMemory(EasyMock.anyObject(), EasyMock.anyObject()))
            .andThrow(new RuntimeException("This is an intentional Exception")).anyTimes();
    
        //attempt 2: stubbing the method
        //exception when executing the test:
         //Unexpected method call putInSharedMemory("foo", com.company.domain.Entity@609fc98)
        Method method = PowerMock.method(CacheWrapper.class, "putInSharedMemory", Object.class, Object.class);
        PowerMock.stub(method).toThrow(new RuntimeException("Exception on purpose."));
    
        //attempt 3: added dependency to Mockito integrated to PowerMock
        //bad idea: the mock created by PowerMock.createMock() belongs to EasyMock, not to Mockito
        //so it breaks when performing the when method
        //exception:
        //java.lang.ClassCastException: org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl
        //cannot be cast to org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl
        //at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.when(PowerMockitoStubberImpl.java:54)
        PowerMockito.doThrow(new RuntimeException("Exception on purpose."))
            .when(cacheWrapper).putInSharedMemory(EasyMock.anyObject(), EasyMock.anyObject());
    
        PowerMock.replay(cacheWrapper);
        return cacheWrapper;
    }
    

    我无法更改 CacheWrapper 因为它来自第三方库。另外,我不能使用 EasyMock#getLastCall ,因为我正在 SomeClient#getEntity 上执行测试。

    I cannot change the implementation of CacheWrapper because it comes from a third party library. Also, I cannot use EasyMock#getLastCall because I'm performing the test on SomeClient#getEntity.

    我怎样才能克服这个问题?

    How can I overcome this?

    推荐答案

    由于你的课程都不是最终的,您可以使用pure mockito而无需使用PowerMockito:

    Since none of your classes are final, you can use "pure mockito" without resorting to PowerMockito:

    final CacheWrapper wrapper = Mockito.spy(new CacheWrapper());
    
    Mockito.doThrow(something)
        .when(wrapper).putInSharedMemory(Matchers.any(), Matchers.any());
    

    注意存根的方法参数实际上是参数匹配器;您可以设置特定值(如果没有被特定方法包围,它将调用 .equals())。因此,您可以针对不同的参数不同地指导存根的行为。

    Note that "method arguments" to a stub are in fact argument matchers; you can put specific values (if not "surrounded" by a specific method it will make a call to .equals()). So, you can guide the stub's behavior differently for different arguments.

    此外,不需要任何类型的 .replay()和Mockito,非常好!

    Also, no need for any kind of .replay() with Mockito, which is very nice!

    最后,请注意你可以 doCallRealMethod() as好。在那之后,这取决于你的场景...

    Finally, be aware that you can doCallRealMethod() as well. After that, it depends on your scenarios...

    (注意:maven上的最后一个mockito版本是1.10.17 FWIW)

    (note: last mockito version available on maven is 1.10.17 FWIW)

    这篇关于如何模拟void方法抛出异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆