我的项目使用JUnit
,Mockito
,PowerMockito
创建单元测试。代码如下:
public class FirstController {
public void doSomething() {
ServiceExecutor.execute();
}
}
public class ServiceExecutor {
private static final List<Service> services = Arrays.asList(
new Service1(),
new Service2(),
...
);
public static void execute() {
for (Service s : services) {
s.execute();
}
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({ServiceExecutor.class})
public class FirstControllerTest {
@Before
public void prepareForTest() {
PowerMockito.mockStatic(ServiceExecutor.class);
PowerMockito.doNothing().when(ServiceExecutor.class)
}
@Test
public void doSomethingTest() {
FirstController firstController = new FirstController();
firstController.doSomething();
PowerMockito.verifyStatic(ServiceExecutor.class, Mockito.times(1));
}
}
此问题的完整源代码:https://github.com/gpcodervn/Java-Tutorial/tree/master/UnitTest
我想验证运行的ServiceExecutor.execute()
方法。
在调用ServiceExecutor
方法时,我尝试模拟doNothing()
和execute()
。但是我对private static final List<Service> services
中的ServiceExecutor
有疑问。它总是为每个服务构造新的实例。每个服务创建新实例的时间更长,如果我模拟每个Service
,我以后将不知道它们将提供多少服务。
您有没有在ServiceExecutor.execute()
中运行任何方法的情况下验证FirstController
中的ServiceExecutor
的想法?
答案 0 :(得分:0)
因此,您知道如何模拟ServiceExecutor.execute,但您不想模拟它。您想在测试中执行它,但不运行测试中的所有service.execute()方法。那不是对FirstController的测试,而是对ServiceExecutor的测试。因此,您可以将问题简化为该问题。
您可以使用反射来更改测试中专用静态字段ServiceExecutor.services的值,如下所述:Change private static final field using Java reflection
public class ServiceExecutorTest {
@Test
public void doSomethingTest() throws NoSuchFieldException, IllegalAccessException {
Field field = null;
List<Service> oldList = null;
try {
field = ServiceExecutor.class.getDeclaredField("services");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
final Service serviceMock1 = mock(Service.class);
final Service serviceMock2 = mock(Service.class);
final List<Service> serviceMockList = Arrays.asList(serviceMock1, serviceMock2);
oldList = (List<Service>) field.get(null);
field.set(null, serviceMockList);
ServiceExecutor.execute();
// or testing the controller
// FirstController firstController = new FirstController();
// firstController.doSomething();
verify(serviceMock1, times(1)).execute();
verify(serviceMock2, times(1)).execute();
} finally {
// restore original value
if (field != null && oldList != null) {
field.set(null, oldList);
}
}
}
static class Service {
void execute() {
throw new RuntimeException("Should not execute");
}
}
static class ServiceExecutor {
private static final List<Service> services = Arrays.asList(
new Service());
public static void execute() {
for (Service s : services) {
s.execute();
}
}
}
}
答案 1 :(得分:0)
您看到的,您正在使用来填充列表
lat
因此,从理论上讲,您可以使用PowerMock(ito)来使用private static final List<Service> services = Arrays.asList(...)
作为控制点。换句话说:您可以让Arrays.asList()
返回想要用于测试的任何列表!
当然,更好的方法可能是将静态列表替换为可以注入的内容,例如
asList()
,其中有一个独特的类为您提供了这样的列表。您可以单独测试该类,然后使用普通的Mockito将模拟的服务提供程序添加到要测试的代码中。
答案 2 :(得分:0)
我发现解决方案使用了@SuppressStaticInitializationFor
注释。
使用此注释可以抑制静态初始化器(构造函数) 一门或多门课。
之所以需要注释,是因为我们需要 在 load-time 知道此类的静态构造函数执行 是否应该跳过。不幸的是,我们不能通过 注释的value参数(并因此获得类型安全的值) 因为这样一来,该类将在PowerMock可以加载之前加载 取消了其构造函数。
https://github.com/powermock/powermock/wiki/Suppress-Unwanted-Behavior
最终代码:
@RunWith(PowerMockRunner.class)
@PrepareForTest({ ServiceExecutor.class })
@SuppressStaticInitializationFor("com.gpcoder.staticblock.ServiceExecutor")
public class FirstControllerTest {
@Before
public void prepareForTest() throws Exception {
PowerMockito.mockStatic(ServiceExecutor.class);
PowerMockito.doNothing().when(ServiceExecutor.class);
}
@Test
public void doSomethingTest() {
FirstController firstController = new FirstController();
firstController.doSomething();
PowerMockito.verifyStatic(ServiceExecutor.class, Mockito.times(1));
}
}