【问题标题】:Creating a thread with a Mockito mocked Runnable使用 Mockito 模拟 Runnable 创建线程
【发布时间】:2015-09-29 02:29:18
【问题描述】:

我有一个实现Runnable 的类,称为DoThingInALoop。它被提供给第二个类,它将从中创建一个线程,调用这个线程BossBoss 可以告诉 runnable 退出并让线程终止。稍后,它可以使用相同的DoThingInALoop 创建一个新线程。

当我为Boss 编写单元测试时,我想模拟DoThingInALoop,因为它不是被测对象。然而,DoThingInALoop 扩展了一个类Loop,而Loop 的代码实际上是在Boss 产生的线程中执行的。该代码当然会遇到NullPointerExceptions,因为没有设置任何成员变量,因为该对象被模拟了。

如何防止 Java 的 Thread“看穿”模拟?

public class Loop implements Runnable {
    private final Object member = new Object();
    public final void run() {
        // This code runs, but it shouldn't
        synchronized (member) { // Throws NPE
            ...
        }
        // Hook for subclasses
        try {
            subclassRun();
        } finally {
            // Mandatory cleanup (unrelated to question)
        }
}

public class DoThingInALoop extends Loop {
    public void subclassRun() { ... }
    public void stopDoingThingsInALoop() {
        // Set instance member that on next loop iteration signals thread to return
    }
}

public class Boss {
    private final DoThingsInALoop toBeMocked;
    public Boss(final DoThingsInALoop toBeMocked) {
        this.toBeMocked = toBeMocked;
    }
    public void start() {
        // Simplified
        new Thread(toBeMocked).start();
    }
    public void stop() {
        toBeMocked.stopDoingThingsInALoop();
    }
}


public class TestClass {
    @Test
    public void aTest() {
        // Setup Mocks
        DoThingsInALoop mockLoop = mock(DoThingsInALoop.class);
        Boss boss = new Boss(mockLoop);

        // Run test
        boss.start();

        // After the above line runs, a new thread is started and
        // the run() method of `Loop` executes until the NPE
        // is hit when it attempts to access a member variable
        // which is of course not set because it is a mocked object

        ...
    }
}

【问题讨论】:

  • 请发布可重现的内容。 Loop#run 不会被调用。一些假的DoThingInALoopProxyClass#run 将被调用,不会做任何事情。
  • 除非你的 run 方法实际上是 final...
  • 你试过在你的模拟中存根run()方法吗? doNothing().when(mockLoop).run();
  • @SotiriosDelimanolis 抱歉,我正在总结真实的代码。 Loop.run() 是最终的并调用另一个方法,该方法是 DoThingInALoop 的挂钩以进行扩展。我会更新问题。
  • @Ruben 我忘了提到 Loop.run() 是最终的,因此不能被嘲笑。

标签: java multithreading unit-testing mockito


【解决方案1】:

我认为这对 Mockito 来说太过分了。

我会将线程执行封装在一个新类中,您可以轻松地模拟它(我们称之为Executor)。

public class Executor {
    public void execute(Runnable runnable) {
        new Thread(runnable).start();
    }
}

然后在创建Thread的地方使用Executor

public class Boss {
    private final DoThingInALoop toBeMocked;
    private final Executor executor;

    public Boss(final Executor executor, final DoThingInALoop toBeMocked) {
        this.executor = executor;
        this.toBeMocked = toBeMocked;
    }

    public void start() {
        executor.execute(toBeMocked);
    }
}

在您的测试中,您只需要模拟Executor

    DoThingInALoop mockLoop = Mockito.mock(DoThingInALoop.class);
    Executor mockExecutor = Mockito.mock(Executor.class);
    Boss boss = new Boss(mockExecutor, mockLoop);

    boss.start();

    // No NPE aynymore
    verify(mockExecutor).execute(mockLoop);

另一种选择是尝试PowerMock

【讨论】:

  • 仅为了单元测试的目的向代码添加间接级别似乎很可惜,但它确实允许模拟。 Executor 可以在第二个构造函数中,默认构造函数填充(runnable) -> new Thread(runnable).start()。这样现有代码可以保持不变。
猜你喜欢
  • 2023-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
  • 1970-01-01
  • 2019-10-27
  • 1970-01-01
  • 2017-03-11
相关资源
最近更新 更多