Mockito 尝试存根与测试中指定的方法不同的方法

时间:2021-05-21 14:04:15

标签: java spring-boot unit-testing mockito

我正在开发一个使用 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 会为从未在测试中调用的 CannotStubVoidMethodWithReturnValue 中的方法 prepare 抛出 AbstractDrainService 异常:

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() {}
}

1 个答案:

答案 0 :(得分:0)

我最终通过将 when 方法调用从 test_drain_service_priority_order_is_respected_for_retries 移动到 setUp 方法来解决这个问题。

相关问题