2015-09-29 68 views
3

我有一個實現Runnable的類,名爲DoThingInALoop。它被賦予第二個類,它將創建一個線程,稱爲線程BossBoss可以告訴runnable退出並讓線程終止。之後,它可以創建一個與此相同的新線程DoThingInALoop用Mockito模擬創建線程Runnable

當我爲Boss編寫單元測試時,我想模擬DoThingInALoop,因爲它不是被測對象。但是,DoThingInALoop擴展了Loop類,並且Loop的代碼實際上正在由Boss產生的線程中執行。該代碼當然會運行到NullPointerException,因爲沒有設置成員變量,因爲該對象被模擬。

如何防止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 

     ... 
    } 
} 
+2

請發佈一些重複性。 'Loop#run'不會被調用。一些虛假的'DoThingInALoopProxyClass#run'將被調用,它將不會執行任何操作。 –

+0

除非你的'run'方法實際上是'final' ... –

+0

你是否在你的模擬中使用了'run()'方法來嘗試? 'doNothing()。when(mockLoop).run();' – Ruben

回答

2

我覺得這對於Mockito來說太過分了。

我會將線程執行封裝在一個可以輕鬆模擬的新類中(我們稱之爲Executor)。

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

然後使用Executor的地方,當你創建Thread

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

+0

似乎有點遺憾,爲了單元測試的目的而在代碼中增加間接級別,但它確實有效地允許嘲笑。 'Executor'可以在第二個構造函數中,默認的構造函數填寫'(runnable) - > new Thread(runnable).start()'。現有的代碼可以保持不變。 – Huckle