我正在编写一个测试类,我想验证一个用 Async 注释并返回 CompletableFuture 的方法是否执行代码分支
我开发了这个Java类ClientTracingServiceImpl:
package com.tutorial.experiment.service.tracing;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import com.tutorial.experiment.component.mapper.IClientTracingMapper;
import com.tutorial.experiment.data.interfaces.TracingMessage;
import com.tutorial.experiment.data.types.ClientTracing;
import com.tutorial.experiment.data.types.EErrorCodes;
import com.tutorial.experiment.data.types.EMessageTopology;
import com.tutorial.experiment.data.types.EStep;
import com.tutorial.experiment.database.db2.entity.tracing.ClientTracingEntity;
import com.tutorial.experiment.database.db2.repository.tracing.IClientTracingRepository;
import com.tutorial.experiment.exception.common.SystemApplicationException;
import com.tutorial.experiment.exception.tracing.ClientTracingNotFoundException;
import com.tutorial.experiment.exception.tracing.InvalidTracingMessageTypeException;
import org.springframework.data.domain.Example;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
@Slf4j
@Service
public class ClientTracingServiceImpl implements IClientTracingService {
private final IClientTracingRepository repository;
private final IClientTracingMapper mapper;
private final JmsTemplate jmsTemplate;
@Async("jmsThreadPoolTaskExecutor")
@Override
public CompletableFuture<Void> notifyTracing(TracingMessage message) throws InvalidTracingMessageTypeException {
String destinationName = "";
EMessageTopology type = message.getType();
if (type == null) {
throw new InvalidTracingMessageTypeException();
}
destinationName = switch (type) {
case REQUEST -> "tracing.client.request";
case RESPONSE -> "tracing.client.response";
default -> throw new InvalidTracingMessageTypeException();
};
jmsTemplate.convertAndSend(destinationName, message);
log.info("Notified message: '{}'", message);
return new CompletableFuture<>();
}
@Timed(value = "find.tracing.by.id.timer", description = "Time of execution of the search a specific tracing by id")
@Override
public ClientTracing findTracingById(Long idTracing, String xRequestId)
throws ClientTracingNotFoundException, SystemApplicationException {
ClientTracing response = null;
try {
ClientTracingEntity queryResult = repository.findById(idTracing)
.orElseThrow(() -> new ClientTracingNotFoundException(idTracing));
response = mapper.entityToData(queryResult);
} catch (ClientTracingNotFoundException e) {
throw e;
} catch (Exception e) {
throw new SystemApplicationException(
"Occurred an internal error while search tracing with id: " + idTracing, e,
EErrorCodes.ENTITY_SEARCH, EStep.FIND_TRACE);
}
log.info("Record found with id: '{}'", idTracing);
return response;
}
@Timed(value = "find.tracing.timer", description = "Time of execution of the search a specific tracing")
@Override
public List<ClientTracing> findTracing(String xRequestId, String requestId, String responseId)
throws ClientTracingNotFoundException, SystemApplicationException {
List<ClientTracing> response = null;
try {
ClientTracingEntity entity = ClientTracingEntity.builder().requestId(requestId).responseId(responseId)
.build();
List<ClientTracingEntity> queryResult = repository.findAll(Example.of(entity));
if (queryResult.isEmpty()) {
throw new ClientTracingNotFoundException(requestId, responseId);
}
response = queryResult.stream()
.map(mapper::entityToData)
.collect(Collectors.toList());
} catch (ClientTracingNotFoundException e) {
throw e;
} catch (Exception e) {
throw new SystemApplicationException(
"Occurred an internal error while search tracing with requestId: " + requestId + " and responseId: "
+ responseId,
EErrorCodes.ENTITY_SEARCH, EStep.MAPPING_TRACE_RESPONSE);
}
return response;
}
}
这是我的测试类ClientTracingServiceImplTest:
package com.tutorial.experiment.service.tracing;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.verify;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.tutorial.experiment.data.interfaces.TracingMessage;
import com.tutorial.experiment.data.types.EErrorCodes;
import com.tutorial.experiment.data.types.EMessageTopology;
import com.tutorial.experiment.data.types.EStep;
import com.tutorial.experiment.database.db2.repository.tracing.IClientTracingRepository;
import com.tutorial.experiment.exception.tracing.InvalidTracingMessageTypeException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.jms.core.JmsTemplate;
@SpringBootTest
class ClientTracingServiceImplTest {
@Autowired
private IClientTracingService iClientTracingService;
@Autowired
private IClientTracingRepository iClientTracingRepository;
@SpyBean
private JmsTemplate jmsTemplate;
private TracingMessage message;
private final ArgumentCaptor<String> queueCaptor = ArgumentCaptor.forClass(String.class);
private final ArgumentCaptor<TracingMessage> messageCaptor = ArgumentCaptor.forClass(TracingMessage.class);
private static final Integer TIMEOUT_SECONDS = 60;
private static final String REQUEST_QUEUE = "tracing.client.request";
private static final String RESPONSE_QUEUE = "tracing.client.response";
@BeforeEach
void init() {
message = new TracingMessage();
message.setApiPath("/test/api");
message.setHttpMethod("GET");
message.setStatusCode("200");
message.setRequestId(UUID.randomUUID().toString());
message.setResponseId(UUID.randomUUID().toString());
}
@AfterEach
void tearDown() {
iClientTracingRepository.deleteAll();
}
@DisplayName("Notify Tracing Message Request")
@Test
void whenNotifyJMSRequestMessage_thenQueueEqualsRequest() {
message.setType(EMessageTopology.REQUEST);
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
iClientTracingService.notifyTracing(message);
});
try {
future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue(!future.isCompletedExceptionally());
verify(jmsTemplate).convertAndSend(queueCaptor.capture(), messageCaptor.capture());
assertEquals(REQUEST_QUEUE, queueCaptor.getValue(), "Queue not match: ".concat(queueCaptor.getValue()));
assertEquals(message, messageCaptor.getValue(), "Original message was modified");
} catch (InterruptedException | ExecutionException | TimeoutException e) {
fail("Async execution failed or timed out");
}
}
@DisplayName("Notify Tracing Message Response")
@Test
void whenNotifyJMSResponseMessage_thenQueueEqualsResponse() {
message.setType(EMessageTopology.RESPONSE);
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
iClientTracingService.notifyTracing(message);
});
try {
future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue(!future.isCompletedExceptionally());
verify(jmsTemplate).convertAndSend(queueCaptor.capture(), messageCaptor.capture());
assertEquals(RESPONSE_QUEUE, queueCaptor.getValue(), "Queue not match: ".concat(queueCaptor.getValue()));
assertEquals(message, messageCaptor.getValue(), "Original message was modified");
} catch (InterruptedException | ExecutionException | TimeoutException e) {
fail("Async execution failed or timed out");
}
}
@DisplayName("Notify Tracing Message With Unknown Message Type")
@Test
void givenException_whenNotifyJMSUnknownMessageType_thenInvalidMessageTypeException() {
message.setType(EMessageTopology.UNKNOWN);
CompletableFuture<Void> future = iClientTracingService.notifyTracing(message);
try {
future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
fail("Expected InvalidTracingMessageTypeException, but no exception was thrown");
} catch (InterruptedException | ExecutionException e) {
Throwable cause = e.getCause();
assertTrue(cause instanceof InvalidTracingMessageTypeException);
InvalidTracingMessageTypeException exception = (InvalidTracingMessageTypeException) cause;
assertEquals(EStep.TRACE_MESSAGE, exception.getStep());
assertEquals(EErrorCodes.LOGICAL_ERROR, exception.getCode());
assertThat(exception.getMessage()).contains("Invalid message type");
} catch (TimeoutException e) {
fail("Timeout waiting for CompletableFuture to complete");
}
}
@DisplayName("Notify Tracing Message With Null Message Type")
@Test
void givenException_whenNotifyJMSNullMessageType_thenInvalidMessageTypeException() {
message.setType(null);
CompletableFuture<Void> future = iClientTracingService.notifyTracing(message);
try {
future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
fail("Expected InvalidTracingMessageTypeException, but no exception was thrown");
} catch (InterruptedException | ExecutionException e) {
Throwable cause = e.getCause();
assertTrue(cause instanceof InvalidTracingMessageTypeException);
InvalidTracingMessageTypeException exception = (InvalidTracingMessageTypeException) cause;
assertEquals(EStep.TRACE_MESSAGE, exception.getStep());
assertEquals(EErrorCodes.LOGICAL_ERROR, exception.getCode());
assertThat(exception.getMessage()).contains("Invalid message type");
} catch (TimeoutException e) {
fail("Timeout waiting for CompletableFuture to complete");
}
}
}
[2023-08-14 09:48:41,911] - [JmsClientTracing-1] - [INFO] - [] - [Notified message: 'TracingMessage(requestId=5ded9d50-aa41-4e9b-ba2d-eabb5125e470, responseId=6e1fa3a2-26f9-47c1-a42c-8d5b2a096830, statusCode=200, apiPath=/test/api, httpMethod=GET, type=REQUEST)']
[2023-08-14 09:48:41,913] - [JmsClientTracing-1] - [DEBUG] - [] - [Method: 'CompletableFuture com.tutorial.experiment.service.tracing.ClientTracingServiceImpl.notifyTracing(TracingMessage)' executed in 34ms]
[2023-08-14 09:48:41,914] - [Thread-2 (ActiveMQ-server-org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl$6@2db55dec)] - [INFO] - [] - [AMQ601265: User anonymous@invm:0 is creating a core consumer on target resource ServerSessionImpl() with parameters: [0, tracing.client.request, null, 0, false, true, null]]
[2023-08-14 09:48:41,922] - [JmsClientTracing-2] - [INFO] - [] - [Notified message: 'TracingMessage(requestId=690743d7-4a42-4d52-a10a-5d1be19211bb, responseId=f9a8feb8-a66e-44b4-9e82-3dbdd9b3c531, statusCode=200, apiPath=/test/api, httpMethod=GET, type=RESPONSE)']
您的测试 whenNotifyJMSResponseMessage_thenQueueEqualsResponse() 执行两个异步操作,并且测试仅在第一个操作中被阻止
测试“givenException_whenNotifyJMSUnknownMessageType_thenInvalidMessageTypeException” 直接调用被测方法。
我不知道为什么测试在单独执行时会完成。我有一些最喜欢的,但没有证据。