- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Test a Quotes API with Spring Boot 4
In this lab, you will gain hands-on experience adding automated tests to a Spring Boot application, focusing on the Quotes REST API. You will explore multiple testing approaches, including using @SpringBootTest, testing endpoints with TestRestTemplate and MockMvc, and using @MockitoBean to mock service dependencies. By the end of the lab, you will be able to create tests that validate application behavior and API responses.
Lab Info
Table of Contents
-
Challenge
Introduction
Welcome to Part 2 of the lab series: Test a Quotes API with Spring Boot 4
This series of labs introduces the core 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 verify the Quotes REST API behaves as expected.
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.gradlefile which imports the Spring Boot Test module. This module includes the testing libraries: JUnit, Jupiter, AssertJ, Hamcrest, Mockito, and other useful libraries.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.
-
Challenge
Introduction to @SpringBootTest and @Autowired
Before testing the REST API features, there are a 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 class for testing.
In the
BasicTestSetup.javafile, you will see an empty class ready to be configured. All of the required imports have already been added to the top of the file.To configure this class for testing, add the
@SpringBootTestannotation above the class declaration:@SpringBootTest public class BasicTestSetup {The
@SpringBootTestannotation configures an application context for testing. It loads the full application context and all defined beans, making it suitable for integration-style testing where you want to verify application behavior in a realistic environment.The first test will exercise the
QuotesServiceclass. As with the application code, this service bean can be injected into the test class by decorating the field containing this bean with the@Autowiredannotation:@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, theQuotesServiceis 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
@Testannotation 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 theQuotesServiceis 2. For this example code, theQuotesServiceis 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, thespring-boot-starter-testhas 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 BasicTestSetupNote: The
--testsflag was added to filter the executed tests to just this current class being worked on.In the terminal output, you should see
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.
Fix this failing test by changing the assertion to expect
3instead of2quotes.After rerunning the test command, the build should pass and display
BUILD SUCCESSFUL.This test has started to test the
QuotesServiceclass directly, but it does not verify if the/api/quotesendpoint is functioning correctly. The next steps will address this concern. -
Challenge
Introduction to TestRestTemplate
The prior test class helped you 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. TheTestRestTemplateis 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.javafile, 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
TestRestTemplateclient can be injected into this test class using the@Autowiredannotation:@Autowired private TestRestTemplate testRestTemplate;To enable the
TestRestTemplatein the test context, add the following annotation to the test class:@AutoConfigureTestRestTemplateThe
TestRestTemplateprovides 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
@SpringBootTestannotation to be:@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureTestRestTemplate public class TestRestTemplateTests {Now you can rewrite the previous test to verify that the
/api/quotesendpoint 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/quotesendpoint and expects the response to be returned as aListobject.The
assertEquals()method performs the same check against the results, but it is 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/quotescan 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:
./gradlew test --tests TestRestTemplateTestsYou 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
3instead of2quotes. Then rerun the test command. The build should now pass and displayBUILD SUCCESSFUL. -
Challenge
Introduction to MockMVC
In the previous step, you used the
TestRestTemplateclass 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
MockMvcclass. This approach allows you to test controller logic without starting an embedded server or sending real HTTP requests. Instead, it performs simulated HTTP requests against your application and verifies the responses.Note: This class also provides additional features, like support for testing JSON and XML payloads, among others.
In the
MockMvcTests.javafile, the required import statements have been added. You can configureMockMvcusing Spring Boot annotations by updating the test class as follows:@SpringBootTest @AutoConfigureMockMvc public class MockMvcTests { @Autowired private MockMvc mockMvc;@AutoConfigureMockMvctells Spring Boot to automatically configure aMockMvcinstance and inject it into the test class using theMockMvcfield with the@Autowiredannotation.Note: If you need more control over the configuration of the
MockMvcinstance, you could use theMockMvcBuildersutility class.Now you can refactor the
quotesEndpointShouldReturn3Quotestest from the previous step to use theMockMvcinstance:@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 Exceptionfor error handling purposes. While theTestRestTemplatehandles potential exceptions in a suppressed manner,MockMvcmanages them directly in a different approach to test such scenarios.The
mockMvc.perform()method accepts aRequestBuilderobject. This object is being built with theMockMvcRequestBuilders.get()method to send a simulated HTTP GET request to the/api/quotesendpoint.Assertions can then be made against the simulated response from this request using various
ResultMatchers. These assertions can be chained together using either theandExpect()orandExpectAll()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
lengthproperty is equal to the expected 3 quotes.
By chaining these methods together, you'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 you were to use multiple lines of code to create the same assertion.
To run this test, execute the following command:
./gradlew test --tests MockMvcTestsYou 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
QuotesServicereturning an exact number of quotes. This will be addressed in the next step. - The response is first checked that the returned status code is
-
Challenge
Introduction to MockitoBean
The previous tests have used the
QuotesServiceinstance directly. This worked in this contrived example because the service always returns three quotes. However, this assumption does not hold in a real application, where the service may depend on other components such as a persistence layer. This can lead to fragile tests that rely on implementation details and external dependencies that should not be part of the test.To help address this, you can mock the
QuotesServiceso that it returns a controlled result and removes the dependency on other layers.In the same
MockMvcTests.javafile, you can add the following code just above the test method:@MockitoBean private QuotesService quotesService;When you add the
@MockitoBeanannotation, Spring replaces the realQuotesServicebean in the application context with a Mockito-based mock.By mocking the
QuotesService, the test for the/api/quotesendpoint is isolated from its underlying dependencies. This allows you to control the data returned by the service and focus only on the behavior of the controller.The
Mockito.when()method can now be used to declare what should be returned when thegetAllQuotes()method is called.At the top of the
quotesEndpointShouldReturn3Quotes()test method, add the following lines to mock what is returned by thegetAllQuotes()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/quotesendpoint.Rerun the gradlew test command for this class file:
./gradlew test --tests MockMvcTestsUnless you update the expected value in the assertion to
2, you will notice that the build fails again.Review 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.
-
Challenge
Next Steps
Congratulations on completing Part 2 of this lab series on Spring!
You can continue working with this lab to see what improvements you can make.
Some ideas are to:
- Create a
MockMvctest for the/api/quotes/0endpoint that mocks thegetQuoteByIndex(int index)method. - Create a
MockMvctest for the POST/api/quotesmethod. Note that this method returns a201 Createdstatus. - Look up the
HamcrestorAssertJassertion 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.
- Create a
About the author
Real skill practice before real-world application
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.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.