DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Keep Your Application Secrets Secret
  • Simplify Java Persistence Using Quarkus and Hibernate Reactive
  • What BDD Is and Why You Need It: Java and Behavior Driven Development
  • Hackathon Java Tools for Developers

Trending

  • Unlocking AI Coding Assistants: Generate Unit Tests
  • Unlocking the Potential of Apache Iceberg: A Comprehensive Analysis
  • Measuring the Impact of AI on Software Engineering Productivity
  • Understanding IEEE 802.11(Wi-Fi) Encryption and Authentication: Write Your Own Custom Packet Sniffer
  1. DZone
  2. Coding
  3. Java
  4. Test-Driven Development With Quarkus

Test-Driven Development With Quarkus

Learn how Quarkus enables test-driven development (TDD) with built-in continuous testing, where tests run immediately after code changes are saved.

By 
Eric Deandrea user avatar
Eric Deandrea
·
Nov. 09, 21 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
5.1K Views

Join the DZone community and get the full member experience.

Join For Free

Many development teams today have adopted test-driven development (TDD). Continuous testing support in Quarkus enables developers to take advantage of this practice. When running Quarkus Dev Mode, you can enable continuous testing with the press of a key, empowering Quarkus to automatically rerun tests affected by a code change in the background.

Quarkus understands which tests are affected by classes and methods within the application. As you make code changes, you get immediate feedback if the change passes your existing test suite. This capability is integrated directly into Quarkus—no IDE or special tooling is required. The future of developer productivity and joy is now!

This article walks you through a TDD approach to building an application and highlights the benefits that Quarkus brings. The completed example you should have after completing the steps in this article can be found in this GitHub repository.

Create a Quarkus Project

Getting started with Quarkus is very simple. All that is required is a Java 11+ JDK, Apache Maven 3.8.1+, and a terminal. An IDE (such as IntelliJ, VSCode, or Eclipse) is helpful but not required. You could go to Code Quarkus to create the project, but we are going to use a terminal instead.

Open up a new terminal and execute:

Shell
 
mvn io.quarkus.platform:quarkus-maven-plugin:2.4.1.Final:create -DprojectGroupId=com.redhat -DprojectArtifactId=quarkus-tdd -DclassName="com.redhat.tdd.TDDResource" -Dpath="/tdd" -Dextensions="resteasy-reactive-jackson"

When complete, you should see the following message:

Shell
 
[INFO] ========================================================================================
[INFO] Your new application has been created in /path/to/quarkus-tdd
[INFO] Navigate into this directory and launch your application with mvn quarkus:dev
[INFO] Your application will be accessible on http://localhost:8080
[INFO] ========================================================================================

Run Quarkus Dev Mode

Quarkus Dev Mode enables hot deployment with background compilation. Changes made to an application while Dev Mode is running will automatically take effect without you having to initiate a recompile or redeploy. In most cases, this happens in under a second.

Your Turn!

cd into the newly-created quarkus-tdd directory and execute ./mvnw quarkus:dev (or mvnw quarkus:dev on Windows).

When complete, you should see:

Shell
 
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
INFO  [io.quarkus] (Quarkus Main Thread) quarkus-tdd 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.4.1.Final) started in 2.218s. Listening on: http://localhost:8080
INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]
 
--
Tests paused
Press [r] to resume testing, [o] Toggle test output, [h] for more options>

The application is now running in Dev Mode.

Enable Continuous Testing

When enabled, Quarkus continuous testing will run affected tests immediately after code changes have been saved, allowing you instant feedback on the changes just made. Quarkus also understands which tests cover which code so only relevant tests are run when changes are made.

Your Turn!

Press r to resume testing. The entire test suite will be run the first time continuous testing is resumed. Doing this allows Quarkus Dev Mode to understand the relationships between the tests and the code they cover.

In this case, there is only one test, so it should execute fairly quickly. When complete, you should see:

Shell
 
All 1 test is passing (0 skipped), 1 test was run in 2739ms. Tests completed at 14:15:25.

Note: The test execution time might vary for you.

Access the Quarkus Dev UI

The Quarkus Dev UI is a landing page that is exposed at the /q/dev URI when running in Dev Mode. The Dev UI allows you to easily browse, visualize, and interact with endpoints offered by extensions currently installed in a project. Each extension can offer its documentation, custom runtime information, full custom pages, and interactive pages with custom actions that can be performed through the Dev UI.

Your Turn!

Press d to open the Dev UI in your browser and click the test icon, as shown in Figure 1.

Figure 1 - Quarkus Dev UI test icon

Figure 1 - Quarkus Dev UI test icon


This will open the test UI, as shown in Figure 2.

Figure 2 - Quarkus Test UI

Figure 2 - Quarkus Test UI


Test-driven Development Scenario

The remainder of this article will focus on implementing and modifying scenarios within the current application being developed. 

  1. In the current project, open src/test/java/com/redhat/tdd/TDDResourceTest.java.
  2. Remove the existing test method in the class.
  3. Add some new tests:
    Java
     
    @Test
    public void getAll() {
      given()
        .when().get("/tdd")
        .then()
          .statusCode(200)
          .body(
            "$.size()", is(1),
            "[0].id", is(1),
            "[0].message", is("Hello")
          );
    }
     
    @Test
    public void getOneFound() {
      given()
        .when().get("/tdd/1")
        .then()
          .statusCode(200)
          .body(
            "id", is(1),
            "message", is("Hello")
          );
    }
     
    @Test
    public void getOneNotFound() {
      given()
        .when().get("/tdd/2")
        .then().statusCode(404);
    }
    • The getAll test issues a GET request to /tdd and expects a status code of 200 and a body containing a JSON array of size 1 that contains an object having attributes id == 1 and message == Hello.
    • The getOneFound test issues a GET request to /tdd/1 and expects a status code of 200 and a body containing a JSON object having attributes id == 1 and message == Hello.
    • The getOneNotFound test issues a GET request to /tdd/2 and expects a status code of 404.
  4. As soon as you save your editor you should see some new test results in the Test UI, as shown in Figure 3. (You might need to refresh the browser page.) The tests are failing because they are calling endpoints that don't currently exist. The getOneNotFound test is passing because the test is asserting that a particular endpoint doesn't return any results, which currently is the case.
    Figure 3 - Quarkus test results (1)
    Figure 3 - Quarkus test results (1)
  5. Create a new class src/main/java/com/redhat/tdd/Item.javawith the following contents:
    Java
     
    package com.redhat.tdd;
     
    public class Item {
      private Long id;
      private String message;
     
      public Item() {
      }
     
      public Item(Long id, String message) {
        this.id = id;
        this.message = message;
      }
     
      public Long getId() {
        return this.id;
      }
     
      public void setId(Long id) {
        this.id = id;
      }
     
      public String getMessage() {
        return this.message;
      }
     
      public void setMessage(String message) {
        this.message = message;
      }
    }
  6. Save Item.java.
  7. Open src/main/java/com/redhat/tdd/TDDResource.java.
  8. Add a new attribute to the class:
    private final List items = List.of(new Item(1L, "Hello"));
  9. Remove all existing methods from the class.
  10. Add a new method that implements the GET to the /tddendpoint:
    Java
     
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Item> getAll() {
      return items;
    }
  11. Save your editor and return to the Test UI to see the updated test results, as shown in Figure 4. You might need to refresh the browser page. The getAll test now passes.
    Figure 4 - Updated test results
    Figure 4 - Updated test results
  12. The functionality implemented by the getOneFound test now needs to be implemented. Still in the TDDResource class, add an additional method that implements the GET to the /tdd/{id} endpoint. This endpoint should return a 200 status code with a JSON representation of the object with id equal to {id}, if one exists. If the id is not found, it should return a 404status code.
    Java
     
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}")
    public Response getOne(@PathParam("id") Long id) {
      return items.stream()
        .filter(item -> id == item.getId())
        .findFirst()
        .map(item -> Response.ok(item).build())
        .orElseGet(() -> Response.status(Status.NOT_FOUND).build());
    }
  13. Save your editor and return to the Test UI and see updated test results, as shown in Figure 5. The completed TDDResourceclass should be:
    Java
     
    package com.redhat.tdd;
     
    import java.util.List;
     
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.core.Response.Status;
     
    @Path("/tdd")
    public class TDDResource {
      private final List<Item> items = List.of(new Item(1L, "Hello"));
     
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public List<Item> getAll() {
        return items;
      }
     
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      @Path("/{id}")
      public Response getOne(@PathParam("id") Long id) {
        return items.stream()
          .filter(item -> id == item.getId())
          .findFirst()
          .map(item -> Response.ok(item).build())
          .orElseGet(() -> Response.status(Status.NOT_FOUND).build());
      }
    }

    Figure 5 - All tests passing

    Figure 5 - All tests passing


  14. Now let's refactor some of the logic into a service class that the resource layer calls rather than the resource layer maintaining the application state. Create a new class src/main/java/com/redhat/tdd/TDDService.javawith the following contents:
    Java
     
    package com.redhat.tdd;
     
    import java.util.List;
    import java.util.Optional;
     
    import javax.enterprise.context.ApplicationScoped;
     
    @ApplicationScoped
    public class TDDService {
      private final List<Item> items = List.of(new Item(1L, "Hello"));
     
      public List<Item> getAllItems() {
        return items;
      }
     
      public Optional<Item> getItem(Long id) {
        return items.stream()
          .filter(item -> id == item.getId())
          .findFirst();
      }
    }
  15. Now let's create some tests for this service. Create a new test class src/test/java/com/redhat/tdd/TDDServiceTests.javawith the following contents:
    Java
     
    package com.redhat.tdd;
     
    import static org.junit.jupiter.api.Assertions.*;
    import org.junit.jupiter.api.Test;
     
    class TDDServiceTests {
      TDDService service = new TDDService();
     
      @Test
      public void getAllItems() {
        var items = service.getAllItems();
        assertTrue(items.size() == 1);
     
        var item = items.get(0);
        assertNotNull(item);
        assertEquals(1L, item.getId());
        assertEquals("Hello", item.getMessage());
      }
     
      @Test
      public void getItemFound() {
        var item = service.getItem(1L);
        assertNotNull(item);
        assertTrue(item.isPresent());
        assertEquals(1L, item.get().getId());
        assertEquals("Hello", item.get().getMessage());
      }
     
      @Test
      public void getItemNotFound() {
        var item = service.getItem(2L);
        assertNotNull(item);
        assertTrue(item.isEmpty());
      }
    }


  16. Save all editors and return to the Test UI to see updated test results, as shown in Figure 6. You might need to refresh your browser. All tests should be passing.
    Figure 6 - All service and resource tests passing
    Figure 6 - All service and resource tests passing
  17. Now we need to refactor the TDDResource class to use the new TDDService. Luckily, we now have a test suite that can make sure our refactoring doesn't affect the application's functionality! Reopen src/main/java/com/redhat/tdd/TDDResource.javaand update it to the following:
    Java
     
    package com.redhat.tdd;
     
    import java.util.List;
     
    import javax.inject.Inject;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.core.Response.Status;
     
    @Path("/tdd")
    public class TDDResource {
      @Inject
      TDDService service;
     
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public List<Item> getAll() {
        return service.getAllItems();
      }
     
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      @Path("/{id}")
      public Response getOne(@PathParam("id") Long id) {
        return service.getItem(id)
          .map(item -> Response.ok(item).build())
          .orElseGet(() -> Response.status(Status.NOT_FOUND).build());
      }
    }
  18. Return to the Test UI. All tests should still be passing.
  19. Return to the terminal where quarkus:dev is running. You should see the following, indicating that only tests for the TDDResourceclass were run:
    Shell
     
    --
    All 6 tests are passing (0 skipped), 2 tests were run in 1255ms. Tests completed at 14:54:44 due to changes to TDDResource.class.
  20. Press q in the terminal where quarkus:dev is running to quit the application.

As you can see from this quick tutorial, continuous testing works across class creation as well as refactoring within an application. Quarkus understands which tests cover code and only reruns affected tests.

The completed example you should have after completing the steps in this article can be found on GitHub.

Where to Learn More

There are many free resources available for learning about and getting started with Quarkus. Why wait for the future? Since its inception in 2019 and continuing today and into the future, Quarkus has provided a familiar and innovative framework for Java developers, supporting capabilities developers need and want today.

Check out these available resources:

  • Read Quarkus for Spring Developers to learn about what challenges led to Quarkus and see side-by-side examples of familiar Spring concepts, constructs, and conventions.
  • Find out why you should choose Quarkus over Spring for microservices development.
  • Discover why organizations believe the developer experience is just as important as the product itself.
  • Explore Quarkus quick starts in the Developer Sandbox for Red Hat OpenShift, which offers a free and ready-made environment for experimenting with containerized applications.
  • Try free 15-minute interactive learning scenarios.
  • Get started with Quarkus on your own.
  • Learn about Quarkus's Spring compatibility features.
    • Find out why, in some instances, there might not be any code changes needed to run a Spring application on Quarkus.
    • Get hands-on converting a Spring Boot application to Quarkus with little-to-no code changes.
  • Attend a Quarkus World Tour event when it comes to a city near you. You can even request a private stop.

This post was originally published on Red Hat Developer. To read the original post, visit https://developers.redhat.com/articles/2021/11/08/test-driven-development-quarkus.

Quarkus Testing Test-driven development dev Spring Framework application Pipeline (software) Java (programming language)

Published at DZone with permission of Eric Deandrea. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Keep Your Application Secrets Secret
  • Simplify Java Persistence Using Quarkus and Hibernate Reactive
  • What BDD Is and Why You Need It: Java and Behavior Driven Development
  • Hackathon Java Tools for Developers

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!