測試場景
單例模式是常見的一種創(chuàng)建型設(shè)計模式,保證了采用該模式的類的實例的全局唯一性。但對于UT來說,由于其屏蔽了類的創(chuàng)建過程,其testability是有待商榷的。
如以下案例,
public class ClassToUseSingleton {
public String invokeSingleton()
{
return Singleton.getInstance().printHelloWorld( "Hi!!!" );
}
}
上述被測應(yīng)用中的invokeSingleton方法調(diào)用了一個Singleton單例類的方法來完成某項特定工作。該單例類的源碼如下:
public class Singleton
{
public String printHelloWorld( String value )
{
StringBuilder stringBuilder
= new StringBuilder( "The string value is: " );
return stringBuilder.append( value ).toString();
}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
Mock實現(xiàn)
通過觀察上述代碼,可以發(fā)現(xiàn)mock的難點在于
- 私有內(nèi)部類
該單例模式采取了內(nèi)部類的方式SingletonInstance來持有一個私有且final的Singleton 對象實例,這樣就保證了Singleton實例的全局唯一性,并且是線程安全的。
private static final Singleton INSTANCE - 靜態(tài)方法/變量
getInstance()是一個靜態(tài)方法,常用的通過new的方式來注入一個mock對象的方法不能使用。
而通過Powermock,則可以解決上述問題。主要思路是,當(dāng)調(diào)用getInstance()方法時,返回一個被mock過的Singleton 實例來替換對SingletonInstance.INSTANCE的調(diào)用。
示例代碼如下
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import static org.junit.Assert.assertEquals;
@RunWith( PowerMockRunner.class )
@PrepareForTest(Singleton.class )
public class ClassToUseSingletonTest
{
@Test
public void testSingeton() throws Exception {
Singleton mockSingleton = PowerMockito.mock(Singleton.class);
Class clazz = Whitebox.getInnerClassType(Singleton.class, "SingletonInstance");
Whitebox.setInternalState(clazz, "INSTANCE", mockSingleton);
PowerMockito.when( mockSingleton.printHelloWorld( Mockito.anyString() ) )
.thenReturn( "Mocked!!" );
assertEquals( "Mocked!!",
new ClassToUseSingleton().invokeSingleton() );
}
}
案例分析
這里主要使用了Whitebox這個工具,
Class clazz = Whitebox.getInnerClassType (Singleton.class, "SingletonInstance");
通過這行代碼,獲取到了內(nèi)部類SingletonInstance。
然后,再將mockSingleton賦給內(nèi)部私有變量 "INSTANCE",
Whitebox.setInternalState(clazz, "INSTANCE", mockSingleton);
這樣,就實現(xiàn)了當(dāng)調(diào)用SingletonInstance.INSTANCE時,將返回被mock過的Singleton對象mockSingleton ,也就是實現(xiàn)了對于單例模式的模擬。