2015-03-02 50 views
9

我有了代碼是這樣的在Java中,我如何模擬使用ServiceLoader加載的服務?

ServiceLoader.load(SomeInterface.class) 

遺留的Java應用程序,我想提供一個模擬實現SomeInterface的此代碼使用。我使用mockito嘲笑框架。

不幸的是,我無法更改遺留代碼,也不希望靜態添加任何東西(例如添加東西到META-INF)。

有沒有一種簡單的方法可以在測試中做到這一點,即。在測試的運行時間?

+0

你能只需更改應用程序代碼爲測試目的? – SpaceTrucker 2015-03-02 09:04:12

回答

6

您可以使用PowerMockito與沿的Mockito嘲笑靜態方法:

@RunWith(PowerMockRunner.class) 
@PrepareForTest(ServiceLoader.class) 
public class PowerMockingStaticTest 
{ 
    @Mock 
    private ServiceLoader mockServiceLoader; 

    @Before 
    public void setUp() 
    { 
     PowerMockito.mockStatic(ServiceLoader.class); 
     Mockito.when(ServiceLoader.load(Mockito.any(Class.class))).thenReturn(mockServiceLoader); 
    } 

    @Test 
    public void test() 
    { 
     Assert.assertEquals(mockServiceLoader, ServiceLoader.load(Object.class)); 
    } 
} 
+0

這可能是最好的答案,儘管我在過去操縱代碼的嘲諷框架(例如,jMockit會導致非常奇怪的行爲)方面有不好的經驗。我可能會將這些遺留代碼所調用的許多地方包含在間接調用中,這將允許我從測試中插入模擬版本的遺留代碼。 – 2015-03-02 16:17:23

+1

@GraemeMoss:另一種想法是,如果ServiceLoader方法在構造時被調用,並且您可以在構建包含類和加載的服務類的實際用法之間「中間」,則可以使用反射來替換在實際使用之前,成員變量持有對加載的服務類的引用並引用您的模擬。 – esaj 2015-03-02 19:42:31

+0

我曾想過這個,但是這個領域是最終的。 :-( – 2015-03-03 18:06:13

2

將調用移入受保護的方法並在測試中覆蓋它。這允許你在測試過程中返回任何東西。

+0

我無法更改舊版代碼。我已經更新了我的問題。 – 2015-03-02 09:24:21

+0

在這種情況下,'PowerMock'是你最好的選擇。請記住,將來總是要包裝靜態方法調用;他們很誘人,但是讓代碼很難或不可能測試。 – 2015-03-02 10:05:43

5

ServiceLoader.load文檔:

爲給定的服務類型新的服務加載器,使用 當前線程的上下文類加載器。

所以,你可以在試運行期間,將動態生成META-INF/service提供者配置文件使用一個特殊的上下文類加載器。上下文類加載器將被用於搜索提供者配置文件,由於該注意ServiceLoader文檔中:

如果用於提供者加載 類加載器的類路徑包含遠程網絡網址,然後將這些在 搜索提供程序配置文件的過程中,URL將被取消引用。

上下文類加載器還需要加載服務類的模擬實現,然後將其作爲模擬實現進行傳遞。

這樣的上下文類加載器需要做兩件事情:

  • 動態地生成上請求的提供者配置文件上請求 每getResource*方法
  • 動態生成一個類(例如 使用ASM library)每loadClass方法,如果它是 類在動態生成的提供程序中指定的 配置文件

使用上述方法,您不需要更改現有的代碼。

+0

這聽起來很重。我將如何創建這樣的類加載器? – 2015-03-02 09:25:55

0

服務通常可以在運行時進行更換。

如果您正在使用的OSGi可以更換服務實現與@BeforeClass註釋的一個設置方法和註銷嘲笑實施的@AfterClass方法:

private ServiceRegistration m_registration; 

@BeforeClass 
public void setUp() { 
    SomeInterface mockedService = Mockito.mock(SomeInterface.class); 
    m_registration = registerService(Activator.getDefault().getBundle(), Integer.MAX_VALUE, SomeInterface.class, mockedService); 
} 

@AfterClass 
public void tearDown() { 
    if (m_registration != null) { 
    unregisterService(m_registration); 
    } 
} 

public static ServiceRegistration registerService(Bundle bundle, int ranking, Class<? extends IService> serviceInterface, Object service) { 
    Hashtable<String, Object> initParams = new Hashtable<String, Object>(); 
    initParams.put(Constants.SERVICE_RANKING, ranking); 
    return bundle.getBundleContext().registerService(serviceInterface.getName(), service, initParams); 
} 

public static void unregisterService(ServiceRegistration registration) { 
    registration.unregister(); 
} 
+1

OSGi!= ServiceLoader。 – bmargulies 2015-03-02 11:28:44

相關問題