如何模拟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的依赖项

以下是我的尝试:

  1. 使用EasyMock.expect 。 这引发了编译器错误。
  2. 试图存根CacheWrapper#putInSharedMemory 。 没有用,因为这个错误消息引发了exception:

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

  3. 为项目添加了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()); 

检查:

的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();