测试场景
单例模式是常见的一种创建型设计模式,保证了采用该模式的类的实例的全局唯一性。但对于UT来说,由于其屏蔽了类的创建过程,其testability是有待商榷的。
如以下案例,
public class ClassToUseSingleton {
public String invokeSingleton()
{
return Singleton.getInstance().printHelloWorld( "Hi!!!" );
}
}
上述被测应用中的invokeSingleton方法调用了一个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实现
通过观察上述代码,可以发现mock的难点在于
- 私有内部类
该单例模式采取了内部类的方式SingletonInstance来持有一个私有且final的Singleton 对象实例,这样就保证了Singleton实例的全局唯一性,并且是线程安全的。
private static final Singleton INSTANCE - 静态方法/变量
getInstance()是一个静态方法,常用的通过new的方式来注入一个mock对象的方法不能使用。
而通过Powermock,则可以解决上述问题。主要思路是,当调用getInstance()方法时,返回一个被mock过的Singleton 实例来替换对SingletonInstance.INSTANCE的调用。
示例代码如下
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");
通过这行代码,获取到了内部类SingletonInstance。
然后,再将mockSingleton赋给内部私有变量 "INSTANCE",
Whitebox.setInternalState(clazz, "INSTANCE", mockSingleton);
这样,就实现了当调用SingletonInstance.INSTANCE时,将返回被mock过的Singleton对象mockSingleton ,也就是实现了对于单例模式的模拟。
网友评论