Featured resource
pluralsight tech forecast
2025 Tech Forecast

Which technologies will dominate in 2025? And what skills do you need to keep up?

Check it out
Hamburger Icon
  • Labs icon Lab
  • Core Tech
Labs

Guided: Creating a Quotes REST API with Spring Boot 3 - Part 2 - Testing

In this lab, you will gain hands-on experience in adding unit tests to a Spring Boot application, specifically focusing on the Quotes REST API. You will explore various testing approaches, including utilizing the `@SpringBootTest` annotation, extending tests using `TestRestTemplate` and `MockMvc`, and leveraging the `MockBean` annotation to mock service requests. By the end of the lab, you will have a solid foundation for creating your own suite of unit tests, enabling you to confidently make modifications to your applications while ensuring their stability and reliability.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 40m
Published
Clock icon Apr 26, 2023

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    Welcome to Part 2 of the lab series:

    Guided: Creating a Quotes REST API with Spring Boot 3.

    This series of labs will guide you through some of the basic concepts for creating a RESTful API Service using Spring Boot with the Spring Web dependency.

    In this lab, you will create several different types of tests that begin to help ensure that the Quotes REST API is working correctly.

    A benefit of using the Spring Initializr template is the added dependency on spring-boot-starter-test. This dependency can be seen in the build.gradle file which imports the Spring Boot Test module. This module includes the testing libraries: JUnit, Jupiter, AssertJ, Hamcrest, Mockito, and other useful libraries. Again, thanks to this Spring Boot template, there is little infrastructure that will need to be coded for testing. Simple annotations will help with testing scenarios.

    The tests in this lab are just an introduction to the possible variations of testing available with Spring Boot Test, and will focus on the Core Spring Boot Test library, including JUnit Jupiter and Mockito.

    To get started with this lab, click the Next step > button below.

  2. Challenge

    Introduction to `@SpringBootTest` and `@Autowired`

    Before testing the REST API features, there are few basic configurations of a Spring Boot Test class that need to be covered. As with most things with Spring Boot, annotations will be used to configure a file as a test class.

    In the BasicTestSetup.java file, you will see an empty class ready to be configured for testing. All of the relevant imports have already been added to the top of the file.

    The first step to configure this class for testing is to add the @SpringBootTest annotation above the class declaration:

    @SpringBootTest
    public class BasicTestSetup {
    

    The @SpringBootTest annotation is used to configure an application context for testing purposes. It loads the entire application context and all the beans defined in it. This makes it suitable for integration testing, where you want to test the behavior of your application in a real environment.

    The first test will exercise the QuotesService class. As with the application code, this service bean can be injected into the test class by decorating the field containing this bean with the @Autowired annotation:

    @Autowired
    private QuotesService quotesService;
    

    When you annotate a field or a constructor parameter with @Autowired, Spring looks for a bean of the same type and injects it automatically. In this case, the QuotesService is being injected into the test class, so you can test its behavior.

    The class is now configured to write the first test against the QuotesService:

    @Test
    public void quotesServiceShouldReturn3Quotes() {
        var quotes = quotesService.getAllQuotes();
        assertEquals(2, quotes.size());
    }
    

    JUnit Jupiter is a popular testing framework for Java applications. It provides several annotations and assertions that make it easy to write and run tests.

    The @Test annotation marks the method as a test method that should be executed by JUnit.

    The assertEquals() assertion checks that the size of the list of all quotes returned from the QuotesService is 2. For this example code, the QuotesService is hardcoded to return 3 quotes so this assertion will fail.

    Note:

    • assertEquals() is one of several assertions available in the Jupiter library. For more advanced assertion methods, the spring-boot-starter-test has included AssertJ and Hamcrest libraries.
    • It is also important to recognize that this is a contrived example. In real-world scenarios, the number of records returned by a service is usually not deterministic. However, this issue will be addressed later on in this lab.

    To verify that this test is currently failing, execute the following command in the Terminal window:

    ./gradlew test --tests BasicTestSetup
    

    Note: The --tests flag was added to filter the executed tests to just this current class being worked on.

    At the command line, you should see that the BUILD FAILED. If you scroll up in the results, you will see some details as to the failure. Specifically a statement providing the path to the created HTML report. For this lab, you can see this report by opening the link {{localhost:8080}}/build/reports/tests/test/index.html.

    This initial page of the report will give an overview of all tests that have been run. A link to the specific failed test(s) can be seen under the Failed tests tab. Clicking on that link will display the reason this particular test failed. As expected, the test failed because it was looking for 2 quotes, but actually returned 3.

    You can fix this failing test changing the assertion to expect 3 instead of 2 quotes.

    Rerunning the test command, the build should now pass and you should see BUILD SUCCESSFUL.

    This test has started to test the QuotesService class directly, but it does not verify if the /api/quotes endpoint is functioning correctly. The subsequent steps will address this concern.

    When ready move to the next step, click the Next step > button below.

  3. Challenge

    Introduction to `TestRestTemplate`

    The prior test class helped to unit test the QuotesService, but does not test whether the REST API is responding correctly. As seen in Part 1 of this series, you could manually test the REST API by starting the server and executing requests against the embedded server.

    The same types of tests can be automated in a test class using the TestRestTemplate. The TestRestTemplate is a class provided by Spring Boot that makes it easy to test RESTful web services. It provides methods for sending HTTP requests and receiving HTTP responses, similar to a web browser or a REST client.

    In the TestRestTemplateTests.java file, the basics you learned in the previous step have been added. Now that you've seen how to write a basic unit test with Spring Boot it is time to write something a little more interesting that actually tests the REST API.

    The TestRestTemplate client can be injected into this test class using the @Autowired annotation:

    @Autowired
    private TestRestTemplate testRestTemplate;
    

    The TestRestTemplate provides methods for sending HTTP requests, which means that this test class will start an embedded web server to handle these requests. So that this embedded web server does not conflict with other applications running on this computer, you can configure the embedded server to use a random port. Modify the @SpringBootTest annotation to be:

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class TestRestTemplateTests {
    

    Now you can rewrite the previous test to verify that the /api/quotes endpoint is configured to correctly respond with the same number of quotes:

    @Test
    public void quotesEndpointShouldReturn3Quotes () {
        var results = this.testRestTemplate.getForObject("/api/quotes", List.class);
        assertEquals(2, results.size()); 
    }
    

    The getForObject() method sends an HTTP GET request to the /api/quotes endpoint and expects the response to be a List object.

    The assertEquals() method performs the same check against the results, still using the wrong number of results. This helps to verify that the test does actually fail when it should.

    Note: The relative URL /api/quotes can be used because the test class is using an embedded server with a random port.

    To verify that this test is currently failing, execute the following command in the Terminal window. Notice the change to pass in this test class:

    ./gradlew test --tests TestRestTemplateTests
    

    You can see the test results report at {{localhost:8080}}/build/reports/tests/test/index.html.

    Note: If you still had the report open from the prior step, but drilled into the failing test, that specific static test class page no longer exists since the above command ran for this particular test class.

    Again, you can fix this failing test by changing the assertion to expect 3 instead of 2 quotes. Then rerunning the test command, the build should now pass with BUILD SUCCESSFUL.

    When ready move to the next step, click the Next step > button below.

  4. Challenge

    Introduction to `MockMVC`

    In the previous step, we used the TestRestTemplate class to send HTTP requests and receive HTTP responses. While this is a useful approach for testing the entire stack, depending on the embedded server, it can be slow and cumbersome to set up.

    In this step, you'll use the MockMvc class. This approach allows us to test our controller logic in isolation, without starting an embedded server or sending real HTTP requests. It provides methods for sending simulated HTTP requests and receiving simulated HTTP responses, similar to a mock HTTP client. This class also provides additional features, like support for testing JSON and XML payloads, among others.

    In the MockMvcTests.java file, the required import statements have been added, and MockMvc can be configured. One way to configure the test class to use MockMvc is to use Spring Boot annotations. Such as the following changes to the test class:

    @SpringBootTest
    @AutoConfigureMockMvc 
    public class MockMvcTests {
        
        @Autowired  
        private MockMvc mockMvc;
    

    @AutoConfigureMockMvc tells Spring Boot to automatically configure a MockMvc instance and inject it into the test class using the mockMvc field with the @Autowired annotation.

    Note: If you need more control over the configuration of the MockMvc instance, you could use the MockMvcBuilders utility class.

    Now you can refactor the quotesEndpointShouldReturn3Quotes test from the previous step to use the mockMvc instance:

    @Test
    public void quotesEndpointShouldReturn3Quotes() throws Exception { 
      this.mockMvc.perform(get("/api/quotes"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.length()").value(3));
    }
    

    Observe that the method signature includes throws Exception for error handling purposes. While the TestRestTemplate handles potential exceptions in a suppressed manner, MockMvc manages them directly in a different approach to test such scenarios.

    The mockMvc.perform() method accepts a RequestBuilder object. This object is being built with the MockMvcRequestBuilders.get() method to send a simulated HTTP GET request to the /api/quotes endpoint.

    Assertions can then be made against the simulated response from this request using various ResultMatchers. These assertions can be chained together using either the andExpect() or andExpectAll() Fluent API methods.

    In this case, the response is first checked that the returned status code is 200 OK. Then the expected JSON response is checked that its length property is equal to the expected 3 quotes.

    By chaining these methods together, we're able to create a single, expressive assertion that checks the length of the JSON array returned by the API. This makes the test more readable and easier to understand than if we were to use multiple lines of code to create the same assertion.

    This test can be verified by running the command:

    ./gradlew test --tests MockMvcTests
    

    You can see the test results report at {{localhost:8080}}/build/reports/tests/test/index.html.

    Compared to the TestRestTemplate, this test should be quicker since it doesn't rely on an embedded server. This is particularly valid when more tests are added.

    This contrived test is still dependent on the QuoteService returning an exact number of quotes. This will be addressed in the next step.

    When ready move to the next step, click the Next step > button below.

  5. Challenge

    Introduction to `MockBean`

    The previous tests have utilized the QuotesService's actual instance, which has functioned well so far as the service returns precisely three quotes in this contrived example. However, this assumption cannot be made for an actual production service, as the service may include other dependencies like a persistence layer. This can result in fragile tests that rely on assumptions and extra layers that should not be tested at this time.

    To help alleviate these issues, the QuotesService can be mocked to return an expected result and remove the dependency on any other layers that the service may also depend on.

    In the same MockMvcTests.java file, you can add the following code just above the test method :

    @MockBean
    private QuotesService quotesService;
    

    When the @MockBean annotation is added to the QuotesService field, a mocked version of the service will be injected. By mocking the QuotesService, the test for the /api/quotes endpoint can be separated from the actual implementation's dependencies, and you can regulate the data returned by the QuotesService while testing.

    The Mockito.when() method can now be used to declare what should be returned when the getAllQuotes() method is called.

    At the top of the quotesEndpointShouldReturn3Quotes() test method, add the following lines to mock what is returned by the getAllQuotes() method:

    @Test
    public void quotesEndpointShouldReturn3Quotes() throws Exception { 
        List<String> quotes = Arrays.asList("quote1", "quote2");
        Mockito.when(quotesService.getAllQuotes()).thenReturn(quotes);
    

    In this case, the mock will return a list of 2 quotes. You can now be certain of any assertions made about the behavior of the /api/quotes endpoint.

    Rerunning the gradlew test command for this class file:

    ./gradlew test --tests MockMvcTests
    

    Unless you changed the expected results to 2, you will notice that the build again failed. Reviewing the test results report at {{localhost:8080}}/build/reports/tests/test/index.html, you will see that it is now returning 2 quotes instead of 3.

    When ready to move to the next step, click the Next step > button below.

  6. Challenge

    Next Steps

    Congratulations on completing Part 2 of this guided lab series on Spring!

    You are welcome to continue working with this lab to see what improvements you can make.

    Some ideas are to:

    • Create a MockMvc test for the /api/quotes/0 endpoint that mocks the getQuoteByIndex(int index) method.
    • Create a MockMvc test for the POST /api/quotes method. Note that this method returns a 201 Created status.
    • Look up the Hamcrest or AssertJ assertion libraries to see some of the advanced assertions that could be used.

    The best to you as you continue your learning journey here at Pluralsight. Hope to see you in Part 3 of this series.

Jeff Hopper is a polyglot solution developer with over 20 years of experience across several business domains. He has enjoyed many of those years focusing on the .Net stack.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.