![](/img/trans.png)
[英]IllegalAccessException when trying to modify private static field
[英]How to modify a static private field during tests?
我的項目使用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()
方法。
調用execute()
方法時,我嘗試模擬ServiceExecutor
和doNothing()
。 但是我對ServiceExecutor
的private static final List<Service> services
有問題。 它總是為每個服務構造新的實例。 每個服務創建新實例的時間更長,如果我模擬每個Service
,我不知道它們以后將擁有多少Service
。
您是否有想法在不運行ServiceExecutor
任何方法的情況下在FirstController
驗證ServiceExecutor.execute()
?
因此,您知道如何模擬ServiceExecutor.execute,但是您不想模擬它。 您想在測試中執行它,但不運行測試中的所有service.execute()方法。 那不是對FirstController的測試,而是對ServiceExecutor的測試。 因此,您可以將問題簡化為該問題。
您可以按如下所述使用反射在測試中更改私有靜態字段ServiceExecutor.services的值: 使用Java反射更改私有靜態最終字段。
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();
}
}
}
}
如評論中所述,“真正的”解決方案是將設計更改為更易於測試的設計。 但考慮到你的限制,這里也許是一個可以工作,另外一個設想,太。
您看到的是,您正在使用
private static final List<Service> services = Arrays.asList(...)
因此,從理論上講,您可以使用PowerMock(ito)來使用Arrays.asList()
作為接管控制的點。 換句話說:您可以讓asList()
返回要用於測試的任何List!
當然,更好的方法可能是將靜態列表替換為可以注入的內容,例如
private final ServicesProvider serviceProvider;
在這里,您有一個獨特的類,可以為您提供此類列表。 您可以單獨測試該類,然后使用普通的Mockito將模擬的服務提供程序添加到要測試的代碼中。
我發現該解決方案使用@SuppressStaticInitializationFor
批注。
使用此注釋可以抑制一個或多個類的靜態初始化器(構造函數)。
之所以需要注釋,是因為我們需要在加載時知道是否應跳過此類的靜態構造函數執行。 不幸的是,我們無法將類作為值參數傳遞給批注(並因此獲得類型安全的值),因為這樣,在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));
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.