javarestintegration-testingcode-coveragejacoco-maven-plugin

How to get code coverage of Rest Assured testcases for my rest endpoints developed in Spring boot?


I am trying to generate jacoco code coverage report for my restassured testcases pointing to the rest end points. I am using springboot application. My controller class looks like :

@RestController
public class ChallengeController {

    private static final Logger LOGGER = LoggerFactory.getLogger(ChallengeController.class);
    @Autowired
    private ChallengeService challengeService;


    public ChallengeController(ChallengeService challengeService) {
        this.challengeService = challengeService;
    }

    


    @GetMapping(path = "/hello",
            produces = MediaType.TEXT_PLAIN_VALUE)
    @ResponseBody
    public String hello() {
        return "hello";
    }
    @GetMapping(path = "/hello/greeting/{name}",
            produces = MediaType.TEXT_PLAIN_VALUE)
    @ResponseBody
    public String greeting(@PathVariable(value = "name") String name) {
        return challengeService.greeting(name);
    }

}

My service class looks like

@Service
@Component
public class ChallengeService {

    
    private static final Logger LOGGER = LoggerFactory.getLogger(ChallengeService.class);



    public ChallengeService() {

    }


    public String greeting(String name) {
        return "hello " + name;
    }
}

My POM looks like

  <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <configuration>
                    <argLine>${surefireArgLine}</argLine>
                    <excludes>
                        <exclude>**/ResilienceTests.java</exclude>
                        <exclude>**/CucumberTest.java</exclude>
                        <exclude>**/DummyChallengeTest.java</exclude>
                    </excludes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco-maven-plugin.version}</version>
                <executions>
                    <execution>
                        <id>pre-unit-test</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                        <configuration>
                            <destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
                            <propertyName>surefireArgLine</propertyName>
                        </configuration>
                    </execution>
                    <execution>
                        <id>post-unit-test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
                            <dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
                            <outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
                        </configuration>
                    </execution>
                    <execution>
                        <id>pre-integration-test</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>prepare-agent-integration</goal>
                        </goals>
                        <configuration>
                            <destFile>${project.build.directory}/coverage-reports/jacoco-it.exec</destFile>
                            <propertyName>failsafeArgLine</propertyName>
                        </configuration>
                    </execution>
                    <execution>
                        <id>post-integration-test</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>report-integration</goal>
                        </goals>
                        <configuration>
                        <!-- excluding everything except controller class -->
                            <excludes>
                                <exclude>**/common/*.class</exclude>
                                <exclude>**/config/*.class</exclude>
                                <exclude>**/domain/*.class</exclude>
                                <exclude>**/exception/*.class</exclude>
                                <exclude>**/mapper/*.class</exclude>
                                <exclude>**/service/*.class</exclude>
                                <exclude>**/simulator/Main.class</exclude>
                            </excludes>
                            <dataFile>${project.build.directory}/coverage-reports/jacoco-it.exec</dataFile>
                            <outputDirectory>${project.reporting.outputDirectory}/jacoco-it</outputDirectory>
                            <sourceEncoding>${project.build.sourceEncoding}</sourceEncoding>
                            <classFilesDIrectory>${project.build.directory}/classes</classFilesDIrectory>
                        </configuration>
                    </execution>
                    <!-- merging jacoco reports-->
                    <execution>
                        <id>report-aggregate</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>report-aggregate</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>merge-results</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>merge</goal>
                        </goals>
                        <configuration>
                            <fileSets>
                                <fileSet>
                                    <directory>${project.build.directory}/coverage-reports/</directory>
                                    <includes>
                                        <include>*.exec</include>
                                    </includes>
                                </fileSet>
                            </fileSets>
                            <destFile>${project.build.directory}/coverage-reports/aggregate.exec</destFile>
                        </configuration>
                    </execution>
                    <execution>
                        <id>post-merge-report</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
                            <dataFile>${project.build.directory}/coverage-reports/aggregate.exec</dataFile>
                            <outputDirectory>${project.reporting.outputDirectory}/jacoco-aggregate</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

     <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${maven-failsafe-plugin.version}</version>
                <executions>
                    <execution>
                        <id>integration-tests</id>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                        <configuration>
                            <argLine>${failsafeArgLine}</argLine>
                            <includes>
                                <include>**/*CucumberTest.java</include>
                                <include>**/*DummyChallengeTest.java</include>
                            </includes>
                            <forkCount>1</forkCount>
                            <reuseForks>true</reuseForks>
                            <!-- When running as a Maven plugin, the JaCoCo agent configuration is prepared by invoking the prepare-agent
                            or prepare-agent-integration goals, before the actual tests are run. This sets a property named argLine which
                            points to the JaCoCo agent, later passed as a JVM argument to the test runner -->

                        </configuration>
                    </execution>
                </executions>
            </plugin>

My test class looks like


@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

public class DummyChallengeTest extends AbstractSteps {



    @Test
    public void testHelloEndpoint() {        
        RestAssured.baseURI = "http://localhost:8082";
        RestAssured.basePath ="/hello";
        Response res = given()
                .when().get()
                .then().log().all()
                .extract().response();
        Assert.assertEquals("hello", res.getBody().asString());
    }

    @Test
    public void testGreetingEndpoint() {
        String uuid = UUID.randomUUID().toString();
        //getServiceUrl() returning me the application base url
        String requestUrl = getServiceUrl() + "/hello/greeting/{name}";
                given()
                .pathParam("name", uuid)
                .when().get(requestUrl)
                .then().log().all()
                .statusCode(200)
                .body(is("hello " + uuid)).log().all().extract();
    }
}

Inside target/site/jacoco-it/jacoco-sessions.html I can locate my controller and service java files.

I am getting partial coverage on my controller file only these line


   @Autowired
    private ChallengeService challengeService;


    public ChallengeController(ChallengeService challengeService) {
        this.challengeService = challengeService;
    }

However I am not getting any coverage on the end points, specifically on these lines

@GetMapping(path = "/hello",
            produces = MediaType.TEXT_PLAIN_VALUE)
    @ResponseBody
    public String hello() {
        return "hello";
    }
    @GetMapping(path = "/hello/greeting/{name}",
            produces = MediaType.TEXT_PLAIN_VALUE)
    @ResponseBody
    public String greeting(@PathVariable(value = "name") String name) {
        return challengeService.greeting(name);
    }

Note : 1. My restassured testcases runs fine and all are passing. 2. When I am using Quarkus application, and Quarkus Test, then this same jacoco implementation giving me full coverage for end points. 3. I am getting full coverage for my Unit testcases.

When I am using Quarkus application, and Quarkus Test, then this same jacoco implementation giving me full coverage for end points.

I have seen some people are saying to run your application and test from same jvm. I am not sure how to do this correctly. Also if this solution is possible to implement in jenkins pipeline.

I have not tried Arquillian yet.


Solution

  • Sorry for posting after so long. I solved this by attaching the jacoco agent to the jvm. You can replicate the same in your local system by following below steps. I have followed the similar approach to implement this solution in my dockerised env.

    Pre req : Download the latest Jacoco distribution from Jacoco's official website. Create a new directory inside the tomcat server directory named 'jaoco'. Copy the jacocoagent.jar, jacocoantjar and jacococli.jar inside the newly created directory. Put your application war file inside the tomcat webaps folder.

    Step 1 : From tomcat /bin folder run command set CATALINA_OPTS="-javaagent:C:/apache-tomcat-9.0.56/jacoco/jacocoagent.jar=destfile=C:/apache-tomcat-9.0.56/jacoco/jacoco.exec,outputfile=file,append=false"

    Jacoco agent should be successfully attached to the JVM now. When we start the tomcat, there should be a jaoco.exec file generated at the provided destination file path.

    Step 2 : Start tomcat locally. Open cmd and then go to tomcat/bin folder and execute command - startup Check that a jacoco.exec file should be generated with 0 kb.

    Step 3 : Make sure the application running on local host should not be up with your actuator end point - /actuator/health

    Step 4 : Run your RestAssured testcases from your ide pointing to the localhost url application url.

    Step 5 : Shutdown the tomact (This is very important. With shutting down reports will not generate)

    Step 6 : heck your jacoco exec file is now have increased in size.

    Step 7 : Create a folder named 'report' inside the jacoco directory available inside jacoco older. We will utilize this folder to generate the html report. No you need to generate a html reort from jacoco.exec file by excuting the command from C:\apache-tomcat-9.056\jacoco

    java -jar C:/apache-tomcat-9.0.56/jacoco/jacococli.jar report jacoco.exec --classfile C:/apache-tomcat-9.0.56/webaps/your-application-local-hasg-SNAPSHOT/WEB-INF/classes --html C:apache-tomcat-9.0.56/jacco/report/

    Note - If you want a specific class instead of all the classes then mention a particular folder, for example classfile C:/apache-tomcat-9.0.56/webaps/your-application-local-hasg-SNAPSHOT/WEB-INF/classes/com/project-foldername/controller This will generate code coverage for only controller classes where you have your end points.