Mockito 试图存根不同于测试中指定的方法
Mockito trying to stub a different method than specified in test
我正在开发一个使用 Java 12、Spring Boot 2.1.4.RELEASE 和 Mockito 2.23.4 的应用程序,我正在尝试将其升级到 Java 15, Spring Boot 2.4.5 和 Mockito 3.6.28(在 spring-boot-dependencies
中提供)。
我目前面临的问题是以下测试:
private AbstractDrainService firstDrainService = mock(AbstractDrainService.class);
private AbstractDrainService secondDrainService = mock(AbstractDrainService.class);
private DrainManager drainManager;
@Before
public void setUp() throws Exception {
when(firstDrainService.hasPendingItems()).thenReturn(true, false);
when(secondDrainService.hasPendingItems()).thenReturn(true, false);
drainManager = new DrainManager();
drainManager.registerDrainService(firstDrainService, DrainManager.Priority.HIGHEST);
drainManager.registerDrainService(secondDrainService, DrainManager.Priority.HIGH);
drainManager.start();
}
public void test_drain_service_priority_order_is_respected_for_retries() throws Exception {
when(firstDrainService.hasPendingRetries()).thenReturn(true, false);
when(secondDrainService.hasPendingRetries()).thenReturn(true, false);
InOrder inOrder = inOrder(firstDrainService, secondDrainService);
inOrder.verify(firstDrainService).retry();
inOrder.verify(secondDrainService).retry();
}
它偶尔会失败,当它失败时,Mockito 会为 AbstractDrainService
中从未在测试中调用的方法 prepare
抛出 CannotStubVoidMethodWithReturnValue
异常:
org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue:
'prepare' is a *void method* and it *cannot* be stubbed with a *return value*!
Voids are usually stubbed with Throwables:
doThrow(exception).when(mock).someVoidMethod();
If you need to set the void method to do nothing you can use:
doNothing().when(mock).someVoidMethod();
For more information, check out the javadocs for Mockito.doNothing().
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. The method you are trying to stub is *overloaded*. Make sure you are calling the right overloaded version.
2. Somewhere in your test you are stubbing *final methods*. Sorry, Mockito does not verify/stub final methods.
3. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
4. Mocking methods declared on non-public parent classes is not supported.
我也试过 doReturn(true, false).when(firstDrainService).retry();
得到了同样的结果。
AbstractDrainService
的实现
/**
* Wraps common drain functionality with constrained retry ability.
*/
public abstract class AbstractDrainService<T> {
/**
* Swaps read and write queues if read queue is empty and write queue is not.
*/
public synchronized void prepare() {}
/**
* Sends items to {@link #doDrain(List)} in {@link #drainSize} sized chunks for processing, until the queue is empty.
* In case of exception, item batch is added to retry list and processed on {@link #retry()} call,
* unless {@link #drainRetryThreshold} is set to 0 in which case failed batches are ignored and not retried
*/
public synchronized void drain() {}
/**
* Retries drain for each previously failed batch until drain succeeds or until {@link #drainRetryThreshold} is reached
*/
public synchronized void retry() {}
/**
* @return {@code true} if there are pending items or {@code false} if the retry queue is empty
*/
public boolean hasPendingRetries() {}
}
和DrainManager
/**
* Controls invocations of {@link AbstractDrainService#drain()} and {@link AbstractDrainService#retry()} on all registered drain services.
* Registered drain services are invoked once every second and in case of errors are retried once every two seconds.
* Services are executed in order dictated by their priority assigned through {@link DrainManager#registerDrainService(AbstractDrainService, int)}.
*/
@Component
public class DrainManager {
private ScheduledExecutorService executorService = null;
private LoggingProperties loggingProperties;
@PostConstruct
public void start() {
executorService = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder().setNameFormat("drain-manager-%d").build());
executorService.scheduleWithFixedDelay(this::drainAll, 0, loggingProperties.getDrain().getPeriod(), TimeUnit.MILLISECONDS);
executorService.scheduleWithFixedDelay(this::retryAll, 0, loggingProperties.getRetry().getPeriod(), TimeUnit.MILLISECONDS);
}
/**
* Registers the given drain service with the given priority.
*/
public void registerDrainService(AbstractDrainService drainService, int priority) {}
/**
* Invokes {@link AbstractDrainService#prepare()} on all registered drain services.
*/
private void prepareAll() {}
/**
* Invokes {@link AbstractDrainService#drain()} on all registered drain services
* for which {@link AbstractDrainService#hasPendingItems()} returns true.
*/
public void drainAll() {}
/**
* Invokes {@link AbstractDrainService#retry()} on all registered drain services
* for which {@link AbstractDrainService#hasPendingRetries()} returns true.
*/
public void retryAll() {}
}
我最终通过将 when
方法调用从 test_drain_service_priority_order_is_respected_for_retries
移动到 setUp
方法解决了这个问题。
我正在开发一个使用 Java 12、Spring Boot 2.1.4.RELEASE 和 Mockito 2.23.4 的应用程序,我正在尝试将其升级到 Java 15, Spring Boot 2.4.5 和 Mockito 3.6.28(在 spring-boot-dependencies
中提供)。
我目前面临的问题是以下测试:
private AbstractDrainService firstDrainService = mock(AbstractDrainService.class);
private AbstractDrainService secondDrainService = mock(AbstractDrainService.class);
private DrainManager drainManager;
@Before
public void setUp() throws Exception {
when(firstDrainService.hasPendingItems()).thenReturn(true, false);
when(secondDrainService.hasPendingItems()).thenReturn(true, false);
drainManager = new DrainManager();
drainManager.registerDrainService(firstDrainService, DrainManager.Priority.HIGHEST);
drainManager.registerDrainService(secondDrainService, DrainManager.Priority.HIGH);
drainManager.start();
}
public void test_drain_service_priority_order_is_respected_for_retries() throws Exception {
when(firstDrainService.hasPendingRetries()).thenReturn(true, false);
when(secondDrainService.hasPendingRetries()).thenReturn(true, false);
InOrder inOrder = inOrder(firstDrainService, secondDrainService);
inOrder.verify(firstDrainService).retry();
inOrder.verify(secondDrainService).retry();
}
它偶尔会失败,当它失败时,Mockito 会为 AbstractDrainService
中从未在测试中调用的方法 prepare
抛出 CannotStubVoidMethodWithReturnValue
异常:
org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue:
'prepare' is a *void method* and it *cannot* be stubbed with a *return value*!
Voids are usually stubbed with Throwables:
doThrow(exception).when(mock).someVoidMethod();
If you need to set the void method to do nothing you can use:
doNothing().when(mock).someVoidMethod();
For more information, check out the javadocs for Mockito.doNothing().
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. The method you are trying to stub is *overloaded*. Make sure you are calling the right overloaded version.
2. Somewhere in your test you are stubbing *final methods*. Sorry, Mockito does not verify/stub final methods.
3. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
4. Mocking methods declared on non-public parent classes is not supported.
我也试过 doReturn(true, false).when(firstDrainService).retry();
得到了同样的结果。
AbstractDrainService
/**
* Wraps common drain functionality with constrained retry ability.
*/
public abstract class AbstractDrainService<T> {
/**
* Swaps read and write queues if read queue is empty and write queue is not.
*/
public synchronized void prepare() {}
/**
* Sends items to {@link #doDrain(List)} in {@link #drainSize} sized chunks for processing, until the queue is empty.
* In case of exception, item batch is added to retry list and processed on {@link #retry()} call,
* unless {@link #drainRetryThreshold} is set to 0 in which case failed batches are ignored and not retried
*/
public synchronized void drain() {}
/**
* Retries drain for each previously failed batch until drain succeeds or until {@link #drainRetryThreshold} is reached
*/
public synchronized void retry() {}
/**
* @return {@code true} if there are pending items or {@code false} if the retry queue is empty
*/
public boolean hasPendingRetries() {}
}
和DrainManager
/**
* Controls invocations of {@link AbstractDrainService#drain()} and {@link AbstractDrainService#retry()} on all registered drain services.
* Registered drain services are invoked once every second and in case of errors are retried once every two seconds.
* Services are executed in order dictated by their priority assigned through {@link DrainManager#registerDrainService(AbstractDrainService, int)}.
*/
@Component
public class DrainManager {
private ScheduledExecutorService executorService = null;
private LoggingProperties loggingProperties;
@PostConstruct
public void start() {
executorService = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder().setNameFormat("drain-manager-%d").build());
executorService.scheduleWithFixedDelay(this::drainAll, 0, loggingProperties.getDrain().getPeriod(), TimeUnit.MILLISECONDS);
executorService.scheduleWithFixedDelay(this::retryAll, 0, loggingProperties.getRetry().getPeriod(), TimeUnit.MILLISECONDS);
}
/**
* Registers the given drain service with the given priority.
*/
public void registerDrainService(AbstractDrainService drainService, int priority) {}
/**
* Invokes {@link AbstractDrainService#prepare()} on all registered drain services.
*/
private void prepareAll() {}
/**
* Invokes {@link AbstractDrainService#drain()} on all registered drain services
* for which {@link AbstractDrainService#hasPendingItems()} returns true.
*/
public void drainAll() {}
/**
* Invokes {@link AbstractDrainService#retry()} on all registered drain services
* for which {@link AbstractDrainService#hasPendingRetries()} returns true.
*/
public void retryAll() {}
}
我最终通过将 when
方法调用从 test_drain_service_priority_order_is_respected_for_retries
移动到 setUp
方法解决了这个问题。