如何模拟void方法抛出exception?
我有这样的结构:
public class CacheWrapper { private Map 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); } }
我的客户端类(你可以说它看起来像这样):
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; } }
我正在为SomeClient#getEntity
方法创建unit testing,并且必须涵盖所有方案。 例如,我需要介绍cacheWrapper
抛出exception的cacheWrapper
。 我正在遵循的方法是为CacheWrapper
类创建一个模拟,使CacheWrapper
类上的方法抛出RuntimeException
,在SomeClient
的实例中设置这个mock并测试Someclient#getEntity
。 问题是当尝试模拟putInSharedMemory
方法因为void
。 我已经尝试了很多方法来做到这一点,但没有一个工作。 该项目具有PowerMock和EasyMock的依赖项 。
以下是我的尝试:
- 使用
EasyMock.expect
。 这引发了编译器错误。 -
试图存根
CacheWrapper#putInSharedMemory
。 没有用,因为这个错误消息引发了exception:java.lang.AssertionError:意外的方法调用putInSharedMemory(“foo”,com.company.domain.Entity @ 609fc98)
-
为项目添加了Mockito依赖项以利用
PowerMockito
类的function。 但这引发了一个例外,因为它没有与EasyMock集成。 这是一个例外:java.lang.ClassCastException:org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl无法强制转换为org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl
以下是此unit testing示例的代码:
@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: is not applicable for EasyMock.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
进行测试。
我怎么能克服这个?
由于您的课程都不是最终的,您可以使用“纯粹的模拟”,而无需使用PowerMockito:
final CacheWrapper wrapper = Mockito.spy(new CacheWrapper()); Mockito.doThrow(something) .when(wrapper).putInSharedMemory(Matchers.any(), Matchers.any());
请注意,存根的“方法参数”实际上是参数匹配器; 你可以放置特定的值(如果没有被特定方法“包围”,它将调用.equals()
)。 因此,您可以针对不同的参数不同地指导存根的行为。
此外,不需要任何类型的.replay()
与Mockito,这是非常好的!
最后,请注意您也可以执行doCallRealMethod()
。 在那之后,这取决于你的场景……
(注意:在maven上可用的最后一个mockito版本是1.10.17 FWIW)
你在使用EasyMock还是Mockito? 两者都是不同的框架。
PowerMockito是可以与这两个框架一起使用的超集(或更多补充)。 PowerMockito允许您执行Mockito或EasyMock不能执行的操作。
尝试使用此方法来存储void方法以抛出exception:
EasyMock的:
// First make the actual call to the void method. cacheWrapper.putInSharedMemory("key", "value"); EasyMock.expectLastCall().andThrow(new RuntimeException());
检查:
- http://easymock.org/api/org/easymock/internal/MocksControl.html#andVoid–
- 获取EasyMock模拟对象以抛出exception
的Mockito:
// Create a CacheWrapper spy and stub its method to throw an exception. // Syntax for stubbing a spy's method is different from stubbing a mock's method (check Mockito's docs). CacheWrapper spyCw = spy(new CacheWrapper()); Mockito.doThrow(new RuntimeException()) .when(spyCw) .putInSharedMemory(Matchers.any(), Matchers.any()); SomeClient sc = new SomeClient(); sc.setCacheWrapper(spyCw); // This will call spyCw#putInSharedMemory that will throw an exception. sc.getEntity("key");
使用expectLastCall
,如:
cacheWrapper.putInSharedMemory(EasyMock.anyObject(), EasyMock.anyObject()) EasyMock.expectLastCall().andThrow(new RuntimeException("This is an intentional Exception")).anyTimes();