Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Test-driven development with Quarkus

November 8, 2021
Eric Deandrea
Related topics:
JavaKubernetesMicroservicesQuarkusSpring Boot
Related products:
Red Hat OpenShift

Share:

    Want to learn more about developing applications with Quarkus? Download our free e-book Quarkus for Spring Developers, which helps Java developers familiar with Spring make a quick and easy transition.

    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:

    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:

    [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:

    __  ____  __  _____   ___  __ ____  ______ 
     --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
     -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
    --\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
    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:

    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.

    Quarkus Dev UI test icon.
    Figure 1. Quarkus Dev UI test icon.

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

    Screenshot of the 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:
      @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.
      Screenshot of the Test UI after the first run.
      Figure 3. Quarkus test results (1).
    5. Create a new class src/main/java/com/redhat/tdd/Item.java with the following contents:
      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 /tdd endpoint:
      @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.
      Screenshot of Test UI after second run.
      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 404 status code.
      @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 TDDResource class should be:
      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());
        }
      }
      Screenshot of Test UI after resource tests pass.
      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.java with the following contents:
      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.java with the following contents:
      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.
      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.java and update it to the following:
      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 TDDResource class were run:
      --
      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.
    Last updated: March 13, 2023

    Related Posts

    • Why should I choose Quarkus over Spring for my microservices?

    • Quarkus for Spring developers: Getting started

    • Quarkus for Spring developers: Kubernetes-native design patterns

    • Learn Quarkus faster in the Developer Sandbox for Red Hat OpenShift

    • Spring Boot on Quarkus: Magic or madness?

    • Enhancing the development loop with Quarkus remote development

    Recent Posts

    • How Kafka improves agentic AI

    • How to use service mesh to improve AI model security

    • How to run AI models in cloud development environments

    • How Trilio secures OpenShift virtual machines and containers

    • How to implement observability with Node.js and Llama Stack

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue