0

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:

enter image description here

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

enter image description here

enter image description here

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?

3
  • 3
    if you add a debug point and run the test in debug mode does it hit the red lines that are not covered? Commented Mar 25 at 22:56
  • No, from what I see when running the tests, when calling the EventRepositoryImpl class from the EventRepositoryImplTest class, when it enters the code that has the try with the timeoutConfig.executeWithTimeout, it is jumping directly to the TimeoutException exception, however, in normal execution this does not happen, could it be because the timeoutconfig class has a timeout configured in the application properties? Commented Mar 27 at 16:02
  • timeout is a mock in your test, you are not using the real timeout class. Anything that the real class might do is irrelevant, you are not using it. Commented Mar 27 at 22:11

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.