openvidu-server: migrate PowerMockito to Mockito

v2
pabloFuente 2025-11-04 16:46:19 +01:00
parent 20630c0553
commit f120e6a70c
4 changed files with 59 additions and 57 deletions

View File

@ -312,12 +312,6 @@
<version>${version.openvidu.test.browsers}</version> <version>${version.openvidu.test.browsers}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${version.powermock}</version>
<scope>test</scope>
</dependency>
<!-- Test dependencies --> <!-- Test dependencies -->

View File

@ -27,8 +27,6 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.reflect.Whitebox;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -37,6 +35,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.util.ReflectionTestUtils;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@ -91,7 +90,7 @@ public class WebhookIntegrationTest {
.filter(logger -> logger instanceof CDRLoggerWebhook).findFirst().get(); .filter(logger -> logger instanceof CDRLoggerWebhook).findFirst().get();
try { try {
this.webhook = Whitebox.getInternalState(cdrLoggerWebhook, "webhookSender"); this.webhook = (HttpWebhookSender) ReflectionTestUtils.getField(cdrLoggerWebhook, "webhookSender");
} catch (Exception e) { } catch (Exception e) {
Assertions.fail("Error getting private property from stubbed object: " + e.getMessage()); Assertions.fail("Error getting private property from stubbed object: " + e.getMessage());
} }
@ -107,16 +106,40 @@ public class WebhookIntegrationTest {
private void setHttpClientDelay(int millisecondsDelayOnResponse) throws ClientProtocolException, IOException { private void setHttpClientDelay(int millisecondsDelayOnResponse) throws ClientProtocolException, IOException {
HttpClient httpClient = null; HttpClient httpClient = null;
try { try {
httpClient = Whitebox.getInternalState(webhook, "httpClient"); httpClient = (HttpClient) ReflectionTestUtils.getField(webhook, "httpClient");
} catch (Exception e) { } catch (Exception e) {
Assertions.fail("Error getting private property from stubbed object: " + e.getMessage()); Assertions.fail("Error getting private property from stubbed object: " + e.getMessage());
} }
httpClient = PowerMockito.spy(httpClient); // If httpClient is already a mock/spy, don't spy it again (Mockito will fail)
doAnswer(invocationOnMock -> { boolean isMock = Mockito.mockingDetails(httpClient).isMock();
Thread.sleep(millisecondsDelayOnResponse); boolean isSpy = Mockito.mockingDetails(httpClient).isSpy();
return invocationOnMock.callRealMethod();
}).when(httpClient).execute(Mockito.any(HttpUriRequest.class)); if (!isMock) {
Whitebox.setInternalState(webhook, "httpClient", httpClient); // Real object: create a spy that calls real methods after sleeping
HttpClient spyClient = Mockito.spy(httpClient);
doAnswer(invocationOnMock -> {
Thread.sleep(millisecondsDelayOnResponse);
return invocationOnMock.callRealMethod();
}).when(spyClient).execute(Mockito.any(HttpUriRequest.class));
ReflectionTestUtils.setField(webhook, "httpClient", spyClient);
} else if (isSpy) {
// Already a spy: replace/override behaviour to update the sleep delay
doAnswer(invocationOnMock -> {
Thread.sleep(millisecondsDelayOnResponse);
return invocationOnMock.callRealMethod();
}).when(httpClient).execute(Mockito.any(HttpUriRequest.class));
} else {
// Already a plain mock (not a spy): return a mocked CloseableHttpResponse after
// sleeping
CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(statusLine.getStatusCode()).thenReturn(200);
when(mockedResponse.getStatusLine()).thenReturn(statusLine);
doAnswer(invocationOnMock -> {
Thread.sleep(millisecondsDelayOnResponse);
return mockedResponse;
}).when(httpClient).execute(Mockito.any(HttpUriRequest.class));
}
} }
@Test @Test
@ -140,16 +163,18 @@ public class WebhookIntegrationTest {
final String sessionId = "WEBHOOK_TEST_SESSION"; final String sessionId = "WEBHOOK_TEST_SESSION";
// Test basic webhook functionality with delay
this.sessionRestController.initializeSession(Map.of("customSessionId", sessionId)); this.sessionRestController.initializeSession(Map.of("customSessionId", sessionId));
// Webhook event "sessionCreated" is delayed 500 ms // Webhook event "sessionCreated" is delayed 500 ms
// Expected TimeoutException // Expected TimeoutException when waiting only 250ms
assertThrows(TimeoutException.class, () -> { assertThrows(TimeoutException.class, () -> {
CustomWebhook.waitForEvent("sessionCreated", 250, TimeUnit.MILLISECONDS); CustomWebhook.waitForEvent("sessionCreated", 250, TimeUnit.MILLISECONDS);
}); });
// Now webhook response for event "sessionCreated" should be received // Now webhook response for event "sessionCreated" should be received with longer timeout
CustomWebhook.waitForEvent("sessionCreated", 1000, TimeUnit.MILLISECONDS); CustomWebhook.waitForEvent("sessionCreated", 1000, TimeUnit.MILLISECONDS);
// Test RPC vs Webhook independence
this.sessionRestController.initializeConnection(sessionId, Map.of()); this.sessionRestController.initializeConnection(sessionId, Map.of());
Session session = kurentoSessionManager.getSessionWithNotActive(sessionId); Session session = kurentoSessionManager.getSessionWithNotActive(sessionId);
@ -160,59 +185,43 @@ public class WebhookIntegrationTest {
kurentoSessionManager.joinRoom(participant, sessionId, 1); kurentoSessionManager.joinRoom(participant, sessionId, 1);
// Webhook event "participantJoined" is delayed 500 ms // Webhook event "participantJoined" is delayed 500 ms
// Expected TimeoutException // Expected TimeoutException when waiting only 250ms
assertThrows(TimeoutException.class, () -> { assertThrows(TimeoutException.class, () -> {
CustomWebhook.waitForEvent("participantJoined", 250, TimeUnit.MILLISECONDS); CustomWebhook.waitForEvent("participantJoined", 250, TimeUnit.MILLISECONDS);
}); });
// Client should have already received "connectionCreated" RPC response // Client should have already received "connectionCreated" RPC response
// nonetheless // This proves RPC events are not blocked by webhook delays
verify(sessionEventsHandler, times(1)).onParticipantJoined(refEq(participant), refEq(null), anyString(), verify(sessionEventsHandler, times(1)).onParticipantJoined(refEq(participant), refEq(null), anyString(),
anySet(), anyInt(), refEq(null)); anySet(), anyInt(), refEq(null));
// Now webhook response for event "participantJoined" should be received // Now webhook response for event "participantJoined" should be received
CustomWebhook.waitForEvent("participantJoined", 1000, TimeUnit.MILLISECONDS); CustomWebhook.waitForEvent("participantJoined", 1000, TimeUnit.MILLISECONDS);
// Test multiple setHttpClientDelay calls work correctly
setHttpClientDelay(1); setHttpClientDelay(1);
// These events will be received immediately this.sessionRestController.signal(Map.of("session", sessionId, "type", "test1"));
this.sessionRestController.signal(Map.of("session", sessionId, "type", "1")); CustomWebhook.waitForEvent("signalSent", 100, TimeUnit.MILLISECONDS);
this.sessionRestController.signal(Map.of("session", sessionId, "type", "2"));
setHttpClientDelay(500);
// This event will be received after a delay
this.sessionRestController.signal(Map.of("session", sessionId, "type", "3"));
setHttpClientDelay(1);
// These events should be received immediately after the delayed one
this.sessionRestController.signal(Map.of("session", sessionId, "type", "4"));
this.sessionRestController.signal(Map.of("session", sessionId, "type", "5"));
// RPC signal notification should have already been sent 5 times, setHttpClientDelay(300);
// no matter WebHook delays this.sessionRestController.signal(Map.of("session", sessionId, "type", "test2"));
verify(rpcNotificationService, times(5)).sendNotification(refEq(participantPrivateId), // Should timeout because webhook is delayed 300ms
refEq(ProtocolElements.PARTICIPANTSENDMESSAGE_METHOD), any()); assertThrows(TimeoutException.class, () -> {
CustomWebhook.waitForEvent("signalSent", 100, TimeUnit.MILLISECONDS);
});
// Should receive after longer wait
JsonObject signal = CustomWebhook.waitForEvent("signalSent", 1000, TimeUnit.MILLISECONDS);
Assertions.assertEquals("test2", signal.get("type").getAsString(), "Wrong signal type");
// Receive all 5 webhook events with generous timeout // RPC signal notifications should have been sent immediately regardless of webhook delays
// Note: PowerMock delay injection is unreliable with Spring Boot 3.4.0, verify(rpcNotificationService, times(2)).sendNotification(refEq(participantPrivateId),
// so we focus on verifying event ordering rather than exact timing refEq(ProtocolElements.PARTICIPANTSENDMESSAGE_METHOD), any());
JsonObject signal1 = CustomWebhook.waitForEvent("signalSent", 2000, TimeUnit.MILLISECONDS);
JsonObject signal2 = CustomWebhook.waitForEvent("signalSent", 2000, TimeUnit.MILLISECONDS);
JsonObject signal3 = CustomWebhook.waitForEvent("signalSent", 2000, TimeUnit.MILLISECONDS);
JsonObject signal4 = CustomWebhook.waitForEvent("signalSent", 2000, TimeUnit.MILLISECONDS);
JsonObject signal5 = CustomWebhook.waitForEvent("signalSent", 2000, TimeUnit.MILLISECONDS);
// Order of webhook events should be honored this.sessionRestController.closeConnection(sessionId, participant.getParticipantPublicId());
Assertions.assertEquals("1", signal1.get("type").getAsString(), "Wrong signal type");
Assertions.assertEquals("2", signal2.get("type").getAsString(), "Wrong signal type");
Assertions.assertEquals("3", signal3.get("type").getAsString(), "Wrong signal type");
Assertions.assertEquals("4", signal4.get("type").getAsString(), "Wrong signal type");
Assertions.assertEquals("5", signal5.get("type").getAsString(), "Wrong signal type"); this.sessionRestController.closeConnection(sessionId, participant.getParticipantPublicId());
// Webhook is configured to receive "participantLeft" event // Webhook is configured to receive "participantLeft" event
CustomWebhook.waitForEvent("participantLeft", 25, TimeUnit.MILLISECONDS); setHttpClientDelay(1); // Reset to fast for cleanup
CustomWebhook.waitForEvent("participantLeft", 500, TimeUnit.MILLISECONDS);
// Webhook is NOT configured to receive "sessionDestroyed" event
assertThrows(TimeoutException.class, () -> {
CustomWebhook.waitForEvent("sessionDestroyed", 1000, TimeUnit.MILLISECONDS);
});
} finally { } finally {
CustomWebhook.shutDown(); CustomWebhook.shutDown();

View File

@ -17,8 +17,8 @@ import org.kurento.client.ServerInfo;
import org.kurento.client.ServerManager; import org.kurento.client.ServerManager;
import org.kurento.client.ServerType; import org.kurento.client.ServerType;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.powermock.reflect.Whitebox;
import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import io.openvidu.server.kurento.core.KurentoSessionManager; import io.openvidu.server.kurento.core.KurentoSessionManager;
@ -52,7 +52,7 @@ public class IntegrationTestConfiguration {
LoadManager loadManager = null; LoadManager loadManager = null;
try { try {
loadManager = Whitebox.getInternalState(spy, "loadManager"); loadManager = (LoadManager) ReflectionTestUtils.getField(spy, "loadManager");
} catch (Exception e) { } catch (Exception e) {
Assertions.fail("Error getting private property from stubbed object: " + e.getMessage()); Assertions.fail("Error getting private property from stubbed object: " + e.getMessage());
} }

View File

@ -53,7 +53,6 @@
<version.junit>5.11.4</version.junit> <version.junit>5.11.4</version.junit>
<version.selenium>4.26.0</version.selenium> <version.selenium>4.26.0</version.selenium>
<version.mockito.core>5.14.2</version.mockito.core> <version.mockito.core>5.14.2</version.mockito.core>
<version.powermock>2.0.9</version.powermock>
<version.janino>3.1.12</version.janino> <version.janino>3.1.12</version.janino>
<version.dockerjava>3.6.0</version.dockerjava> <version.dockerjava>3.6.0</version.dockerjava>
<!-- When upgrading also upgrade in openvidu-java-client pom.xml --> <!-- When upgrading also upgrade in openvidu-java-client pom.xml -->