Good afternoon, I'm testing a microservice with Junit and Mockito in Quarkus (a Java framework). I'm also using Jacobo for Covegare. However, the EventRepositoryImplTest class, which simulates database searches, isn't included. The main class is as follows:
EventRepository
package com.tmve.customer.oracle.repository;
import java.util.HashMap;
public interface EventRepository {
HashMap<String, String> findValidateNumberStatus(com.cies.grpc.proto.ValidateNumberActivationRequest request);
}
EventRepositoryImpl
package com.tmve.customer.oracle.repository.impl;
import com.tmve.customer.config.TimeoutConfig;
import com.tmve.customer.oracle.repository.EventRepository;
import com.tmve.customer.util.CustomLogger;
import com.tmve.customer.util.LogMessage;
import com.tmve.customer.exceptions.DatabaseUnavailableException;
import com.tmve.customer.exceptions.NotFound;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import jakarta.transaction.Transactional;
import java.sql.SQLException;
import java.util.HashMap;
@ApplicationScoped
public class EventRepositoryImpl implements EventRepository {
@Inject
public EntityManager entityManager;
@Inject
public TimeoutConfig timeoutConfig;
@Inject
public CustomLogger logger;
@Override
@Transactional
public HashMap<String, String> findValidateNumberStatus(com.cies.grpc.proto.ValidateNumberActivationRequest request) {
HashMap<String, String> resMap = new HashMap<>();
String msisdn = request.getMsisdn().isBlank() ? "0" : request.getMsisdn();
/*logger.logInfo(this, LogMessage.DATABASE_OPERATION_STARTED.getMessage());*/
/*try {*/
try {
return timeoutConfig.executeWithTimeout(() -> {
logger.logInfo(this, LogMessage.DATABASE_OPERATION_STARTED.getMessage());
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("CES_PACK_ADMIN_NUMEROS.validarnumeroactivacion");
logger.logInfo(this, LogMessage.MAPPING_REQUEST_STARTED.getMessage());
query.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN);
query.registerStoredProcedureParameter(2, String.class, ParameterMode.OUT);
query.registerStoredProcedureParameter(3, String.class, ParameterMode.OUT);
query.registerStoredProcedureParameter(4, String.class, ParameterMode.OUT);
query.registerStoredProcedureParameter(5, String.class, ParameterMode.OUT);
query.setParameter(1, Long.parseLong(msisdn));
logger.logInfo(this, LogMessage.MAPPING_REQUEST_COMPLETED.getMessage());
query.execute();
resMap.put("status", (String) query.getOutputParameterValue(2));
resMap.put("statusDate", (String) query.getOutputParameterValue(3));
resMap.put("registerDate", (String) query.getOutputParameterValue(4));
resMap.put("reason", (String) query.getOutputParameterValue(5));
if (resMap.get("reason").equals("El numero no existe")) {
logger.logInfo(this, LogMessage.MAPPING_ERROR.getMessage(), request);
throw new NotFound("No existe el número en BD CIES");
}
logger.logInfo(this, LogMessage.DATABASE_OPERATION_COMPLETED.getMessage(), resMap);
return resMap;
});
}
catch (NotFound e) {
throw e;
}
catch (org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException e) {
logger.logError(this, LogMessage.DATABASE_OPERATION_ERROR.getMessage(), e);
throw new DatabaseUnavailableException("Timeout alcanzado durante la operación de base de datos", e);
}
catch (SQLException e) {
logger.logError(this, LogMessage.DATABASE_OPERATION_ERROR.getMessage(), e);
throw new DatabaseUnavailableException("El backend de la base de datos no está disponible",e);
}
catch (Exception e) {
logger.logError(this, LogMessage.DATABASE_OPERATION_ERROR.getMessage(), e);
throw new DatabaseUnavailableException("Error inesperado al procesar la consulta: " + e.getMessage(), e);
}
}
}
The class I am using to simulate Junit/Jacoco tests is the following:
EventRepositoryImplTest
package com.tmve.customer.oracle;
import com.cies.grpc.proto.ValidateNumberActivationRequest;
import com.tmve.customer.config.TimeoutConfig;
import com.tmve.customer.exceptions.DatabaseUnavailableException;
import com.tmve.customer.exceptions.NotFound;
import com.tmve.customer.oracle.repository.impl.EventRepositoryImpl;
import com.tmve.customer.util.CustomLogger;
import com.tmve.customer.util.LogMessage;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.concurrent.Callable;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@QuarkusTest
class EventRepositoryImplTest {
@Mock
private EntityManager entityManager;
@Mock
private StoredProcedureQuery query;
@InjectMocks
private EventRepositoryImpl eventRepository;
@Mock
private CustomLogger logger;
@Mock
private TimeoutConfig timeoutConfig;
@BeforeEach
void setUp() {
entityManager = mock(EntityManager.class);
query = mock(StoredProcedureQuery.class);
logger = mock(CustomLogger.class);
timeoutConfig = mock(TimeoutConfig.class);
eventRepository = new EventRepositoryImpl();
eventRepository.entityManager = entityManager;
eventRepository.logger = logger;
eventRepository.timeoutConfig = timeoutConfig;
}
@Test
@DisplayName("Test FindNumberValidation in BD within EventRepositoryImpl")
void testFindNumberValidationInBD_SuccessfulExecution() throws Exception {
HashMap<String,String> resMap = mock(HashMap.class);
ValidateNumberActivationRequest request = buildValidateNumberActivationRequest();
when(entityManager.createStoredProcedureQuery(anyString())).thenReturn(query);
query.registerStoredProcedureParameter(1,Integer.class, ParameterMode.OUT);
query.registerStoredProcedureParameter(2,String.class, ParameterMode.OUT);
query.registerStoredProcedureParameter(3,String.class, ParameterMode.OUT);
query.registerStoredProcedureParameter(4,String.class, ParameterMode.OUT);
query.registerStoredProcedureParameter(5,String.class, ParameterMode.OUT);
query.setParameter(1, Long.parseLong(request.getMsisdn()));
query.execute();
verify(query).registerStoredProcedureParameter(1,Integer.class, ParameterMode.OUT);
verify(query).registerStoredProcedureParameter(2,String.class, ParameterMode.OUT);
verify(query).registerStoredProcedureParameter(3,String.class, ParameterMode.OUT);
verify(query).registerStoredProcedureParameter(4,String.class, ParameterMode.OUT);
verify(query).registerStoredProcedureParameter(5,String.class, ParameterMode.OUT);
verify(query).setParameter(1, Long.parseLong(request.getMsisdn()));
verify(query).execute();
when(query.getOutputParameterValue(2)).thenReturn("OC");
when(query.getOutputParameterValue(3)).thenReturn("25/12/24");
when(query.getOutputParameterValue(4)).thenReturn("21/09/06");
when(query.getOutputParameterValue(4)).thenReturn("El numero tiene estado de validación");
when(resMap.get("status")).thenReturn("OC");
when(resMap.get("statusDate")).thenReturn("25/12/24");
when(resMap.get("registerDate")).thenReturn("21/09/06");
when(resMap.get("reason")).thenReturn("El numero tiene estado de validación");
assertEquals("OC", resMap.get("status"));
assertEquals("25/12/24", resMap.get("statusDate"));
assertEquals("21/09/06", resMap.get("registerDate"));
assertEquals("El numero tiene estado de validación", resMap.get("reason"));
}
@Test
void testFindNumberValidation_DatabaseTimeout() throws Exception {
ValidateNumberActivationRequest request = ValidateNumberActivationRequest.newBuilder()
.setMsisdn("145869997")
.build();
doThrow(new org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException("Timeout alcanzado"))
.when(timeoutConfig).executeWithTimeout(any());
DatabaseUnavailableException exception = assertThrows(DatabaseUnavailableException.class, () ->
eventRepository.findValidateNumberStatus(request)
);
assertEquals("Timeout alcanzado durante la operación de base de datos", exception.getMessage());
verify(logger).logError(eq(eventRepository), eq(LogMessage.DATABASE_OPERATION_ERROR.getMessage()), any(Exception.class));
}
@Test
void testFindNumberValidation_DatabaseUnavailable() throws Exception {
ValidateNumberActivationRequest request = ValidateNumberActivationRequest.newBuilder()
.setMsisdn("146480755")
.build();
when(entityManager.createStoredProcedureQuery(anyString())).thenAnswer(i -> {throw new SQLException("Exception SQL");});
doAnswer(invocation -> {
((Callable<Void>) invocation.getArgument(0)).call();
return null;
}).when(timeoutConfig).executeWithTimeout(any());
DatabaseUnavailableException exception = assertThrows(DatabaseUnavailableException.class, () -> {
eventRepository.findValidateNumberStatus(request);
});
assertTrue(exception.getMessage().contains("El backend de la base de datos no está disponible"));
verify(logger).logError(eq(eventRepository), eq(LogMessage.DATABASE_OPERATION_ERROR.getMessage()), any(SQLException.class));
}
@Test
void testFindNumberValidation_UnaVailable() throws Exception {
ValidateNumberActivationRequest request = ValidateNumberActivationRequest.newBuilder()
.setMsisdn("146480755")
.build();
when(entityManager.createStoredProcedureQuery(anyString())).thenAnswer(i -> {throw new Exception("Custom test exception");});
doAnswer(invocation -> {
((Callable<Void>) invocation.getArgument(0)).call();
return null;
}).when(timeoutConfig).executeWithTimeout(any());
DatabaseUnavailableException exception = assertThrows(DatabaseUnavailableException.class, () -> {
eventRepository.findValidateNumberStatus(request);
});
assertTrue(exception.getMessage().contains("Error inesperado al procesar la consulta"));
verify(logger).logError(eq(eventRepository), eq(LogMessage.DATABASE_OPERATION_ERROR.getMessage()), any(Exception.class));
}
@Test
void testFindNumberValidation_NotFound() throws Exception {
ValidateNumberActivationRequest request = ValidateNumberActivationRequest.newBuilder()
.setMsisdn("1464807")
.build();
when(entityManager.createStoredProcedureQuery(anyString())).thenAnswer(i -> {throw new NotFound("No existe el número en BD CIES");});
doAnswer(invocation -> {
((Callable<Void>) invocation.getArgument(0)).call();
return null;
}).when(timeoutConfig).executeWithTimeout(any());
NotFound exception = assertThrows(NotFound.class, () -> {
eventRepository.findValidateNumberStatus(request);
});
assertTrue(exception.getMessage().contains("No existe el número en BD CIES"));
}
private ValidateNumberActivationRequest buildValidateNumberActivationRequest() {
return ValidateNumberActivationRequest.newBuilder().setMsisdn("145869997").build();
}
}
When I run the tests, I see that the EventRepositoryImplTest test class runs without any problems:
This is how I have my pom.xml configured:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.tmve.customer</groupId>
<artifactId>validate-number-activation-grpc</artifactId>
<version>1.0.4-DEV</version>
<properties>
<compiler-plugin.version>3.13.0</compiler-plugin.version>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.19.2</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.5.2</surefire-plugin.version>
<!-- sonar -->
<sonar.projectKey>validate-number-activation</sonar.projectKey>
<sonar.projectName>validate-number-activation</sonar.projectName>
<sonar.sources>src/main/java</sonar.sources>
<sonar.tests>src/test/java</sonar.tests>
<sonar.java.binaries>target/classes</sonar.java.binaries>
<sonar.java.test.binaries>target/test-classes</sonar.java.test.binaries>
<sonar.coverage.jacoco.xmlReportPaths>target/jacoco-report/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-grpc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-oracle</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-jdbc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-grpc-common</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jacoco</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-fault-tolerance</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
<goal>native-image-agent</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>4.0.0.4121</version>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>
application.properties
quarkus.application.name=validate-number-activation-grpc
path=/
##---------------------
##grpc configurations
##---------------------
quarkus.grpc.server.port=${PORT:9001}
quarkus.http.port=8080
##---------------------
#open telemetry configurations
##---------------------
quarkus.otel.enabled=true
quarkus.otel.metrics.enabled=true
quarkus.otel.traces.eusp.enabled=true
quarkus.otel.exporter.otlp.endpoint=${QUARKUS_OTEL_EXPORTER_OTLP_ENDPOINT:http://xxxxxxxxx}
quarkus.otel.exporter.otlp.traces.protocol=grpc
quarkus.otel.exporter.otlp.metrics.protocol=grpc
quarkus.otel.exporter.otlp.protocol=grpc
quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, parentId=%X{parentId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n
##---------------------
##jacoco configurations
##---------------------
quarkus.jacoco.enabled=true
quarkus.jacoco.report=true
quarkus.jacoco.report-location=target/jacoco-report
quarkus.jacoco.excludes=/com/cies/grpc/proto/
##---------------------
##database configurations
##---------------------
quarkus.datasource.db-kind=oracle
quarkus.datasource.jdbc.url=${QUARKUS_DATASOURCE_JDBC_URL:jdbc:oracle:thin:@xxxxxxxxxx/IVR}
quarkus.datasource.jdbc.acquisition-timeout=${QUARKUS_DATASOURCE_JDBC_ACQUISITION_TIMEOUT:500}
quarkus.datasource.jdbc.driver=oracle.jdbc.driver.OracleDriver
quarkus.hibernate-orm.dialect=org.hibernate.dialect.OracleDialect
quarkus.datasource.db-version=${QUARKUS_DATASOURCE_DB_VERSION:11.2.0}
quarkus.datasource.jdbc.min-size=10
quarkus.datasource.jdbc.max-size=50
timeout.connection-config=${CONFIG_TIMEOUT_DB:10000}
quarkus.live-reload.instrumentation=true
quarkus.hibernate-orm.log.sql=false
quarkus.hibernate-orm.database.generation=none
# ---------
# QUARKUS CONFIGURATION
# ---------
quarkus.native.add-all-charsets=true
quarkus.hibernate-orm.validate-in-dev-mode=false
quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, parentId=%X{parentId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n
quarkus.native.additional-build-args=-march=compatibility,-H:IncludeResources=.*\\.jks
quarkus.native.container-build=true
This is how it shows up in the jacoco report:
\target\jacoco-report
index.html
The TimeoutConfig class is added, which is a modification of the FaulTolererance and will be called within the execution of the EventRepositoryImpl and the findValidateNumberStatus method, which if a certain time has passed (which is configured from the application.properties) throws a TimeoutException type exception:
TimeoutConfig
package com.tmve.customer.config;
import io.smallrye.faulttolerance.api.FaultTolerance;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.Callable;
@ApplicationScoped
public class TimeoutConfig {
@ConfigProperty(name = "timeout.connection-config")
long timeout;
public <T> T executeWithTimeout(Callable<T> task) throws Exception {
FaultTolerance<T> faultTolerance = FaultTolerance.<T>create()
.withTimeout()
.duration(timeout, ChronoUnit.MILLIS)
.done()
.build();
return faultTolerance.call(task);
}
}
What could be happening? Why isn't my coverage covering me?


