Testing Spring Boot Applications Demystified (Philip Riecks) (Z-Library)

Author: Philip Riecks

商业

Unravel the complexities of testing Spring Boot applications. Gain insights, best practices, practical tips and avoid common pitfalls to write comprehensive and effective tests to become more productive. Demystify Spring Boot testing and deliver robust & maintainable applications. Start your testing journey today!

📄 File Format: PDF
💾 File Size: 866.3 KB
47
Views
0
Downloads
0.00
Total Donations

📄 Text Preview (First 20 pages)

ℹ️

Registered users can read the full content for free

Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.

📄 Page 1
(This page has no text content)
📄 Page 2
Testing Spring Boot Applications Demystified Avoiding Pitfalls, Implementing Recipes, and Embracing Best Practices Philip Riecks This book is for sale at http://leanpub.com/testing-spring-boot-applications-demystified This version was published on 2023-07-04 This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do. © 2023 Philip Riecks
📄 Page 3
Also By Philip Riecks Stratospheric Java Testing Toolbox
📄 Page 4
Contents Intro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 About this Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 About the Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Spring Boot Testing Demystified . . . . . . . . . . . . . . . . . . . . . . . . 3 Spring Boot Testing Pitfalls . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Tips & Tricks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Recipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Outro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Next Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Further Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Changelog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
📄 Page 5
Intro About this Book Whether you’re a seasoned developer or just starting your journey with Spring Boot, this ebook is designed to unravel the complexities of testing Spring Boot applications and empower you to become more productive and confident in your testing efforts. Testing plays a crucial role in software development, allowing you to catch bugs, ensure your code functions as intended, and provide confidence in the reliability and stability of your applications. However, testing Spring Boot applications can sometimes feel like navigating a labyrinth of challenges – frommanagingdependencies and external systems to crafting effective tests that reflect the behavior of your code. In this ebook,wewill demystify testingSpringBoot applicationsbyproviding you with clear explanations, practical insights, and actionable best practices. We will guide you through the common pitfalls faced by developers, share time-tested recipes for success, and equip you with the tools and knowledge you need to write comprehensive and effective tests. More than just a technical guide, “Testing Spring Boot Applications De- mystified” aims to motivate and inspire you on your testing journey. We believe that thorough testing is not only a means to catch bugs but also a pathway to building robust, high-quality applications. By implementing the best practices and strategies outlined in this ebook, you will gain the confidence to deliver software that exceeds expectations and delights both users and stakeholders.
📄 Page 6
Intro 2 So, whether you are looking to enhance your testing skills or seeking guid- ance on the unique challenges faced in testing Spring Boot applications, this ebook is your go-to resource. Get ready to demystify testing, overcome obstacles, and become a proficient tester of Spring Boot applications. About the Author Under the slogan “Testing Java Applications Made Simple” Philip provides recipes and tips & tricks to accelerate your testing success and make testing joyful (or at least less painful). Apart from blogging, he’s a course instructor for various Java-related online courses and active on YouTube. Findoutmore about Philip on theTesting JavaApplicationsMadeSimple blog and follow him on Twitter.
📄 Page 7
Spring Boot Testing Demystified Spring Boot Testing Pitfalls Learning how to test a Spring Boot application effectively can be a hurdle, especially fornewcomers.Without abasic knowledgeof Spring’s dependency injectionmechanism andwhat Spring Boot’s auto-configuration is all about, we might end up throwing annotations to our test. Trying tomake thingswork.While this trial and errormight fix the test setup for some cases, the result is usually a not-so-optimal test setup. With this section post, I’ve collected the most common pitfalls I’ve seen in projects and while answering questions on Stack Overflow when it comes to testing Spring Boot applications. Spring Boot Testing Pitfall 1: @Mock vs. @MockBean One of the first Spring Boot testing pitfalls is mocking collaborators, the objects our class under test depends on. If we are already familiar with Mockito, we might know that we can use @Mock to create mocks for our unit tests. When writing tests for our Spring Boot applications, we don’t have to un-learn any specificMockito knowledge. Nevertheless, we have to be aware ofwhat kindof testwe’rewriting.Does our testworkwith orwithout a Spring TestContext? That’s important because it determines whether to use @Mock vs. @Mock- Bean. While both annotations create a mocked version of our collaborators, @Mock is only relevant for plain unit tests that work without a Spring Test- Context. In such cases, we usually create mocks of the collaborators and
📄 Page 8
Spring Boot Testing Demystified 4 inject them via the public constructor of our class under test. For tests that work with a Spring TestContext, e.g., when using a Spring Boot Test slice annotation or@SpringBootTest, things work differently. Here, we still want to mock the collaborator of your class under test. But this time, Spring assembles all our beans and performs dependency injection. Hence, we have to replace (or add) amocked version of collaborator as a bean inside the Spring TestContext. That’swhere @MockBean comes into play.Weuse it on top of a test field to instruct Spring Test to add amocked version of this bean inside our TestContext. Whether we use @Mock or @MockBean, the Mockito stubbing setupworks the same for both. The pitfall here lies in eithermixing both annotations in the same test or using one of the annotations for the wrong purpose. I’ve covered a comparison of both annotations and when to use them in a separate @Mock vs. @MockBean blog post. Spring Boot Testing Pitfall 2: Extensive Usage of @SpringBootTest When startingwith testing Spring Boot applications, we’ll soon stumble over the @SpringBootTest annotation. Spring Boot even creates a basic test that uses this annotation for each new project generated from start.spring.io. The name of the annotation might imply it’s used and required for every Spring Boot test. That’s not the case.We use@SpringBootTestwheneverwewant to write an integration test that works with the entire Spring Context. Spring Test will create a TestContext for us that contains all our beans (@Component, @Configuration, @Service, etc.). This implies that we also have to provide every external infrastructure component we’re connecting to. Imagine we’re writ- ing a CRUD application that connects to a database. We won’t be able to create and use our repository classes if there’s no database during test execution to connect to. If we would only use @Spring- BootTest for our tests,we’ll soon encounter that our test suite takesway longer than plain JUnit & Mockito tests. Starting a Spring Context results in slower
📄 Page 9
Spring Boot Testing Demystified 5 test execution times as everything has to be instantiated and initialized. As a general recommendation, we should try to test and verify as much of our implementation as possible on a lower testing level. Thatmeans a specific if- block inside our @Service class can be tested with a unit test. Furthermore, we can ensure our Spring Security configuration is working by using @WebMvcTest. For integration tests that verify the interaction for multiple components, or when writing end-to-end tests, @SpringBootTest comes into play. Make yourself familiar with the different Spring Boot test slice annotations. Fur- thermore, there are several ways to further tweak @SpringBootTest, which we have to be aware of. Testing Pitfall 3: Not Testing At All I guess this Spring Boot testing pitfall goes without saying. If we’re not testing our code, how can we ever say it’s working? While we might have checked our implementation manually, how can we ensure any upcoming changes don’t break our feature? If we don’t test our application, our users definitely will, and they won’t be delighted if they find half-backed features. Testing might not be the first priority when learning Spring Boot. That’s fine as long as we’re making sure to return to the testing topic as soon as we feel comfortable with the framework. Whether we write the test before the implementation (aka. test-driven development) or afterward depends on personal preferences. I’ve had a great experience writing the test first, leading to more thoughtful design and smaller steps. Doing it the other way around and adding tests to our code right after we finish the implementation usually results in not-so-well tests. We already know how the implementation looks like and are biased towards testing only the bare minimum. On top of this, we might already be late integrating our changes and, hence, have little time to test the implementation thoroughly. The Spring Framework and Spring Boot emphasize the importance of testing
📄 Page 10
Spring Boot Testing Demystified 6 and encourage us to write tests by having great testing support and tools. Testing is an essential part of every Spring Boot project as every new project already comes with a basic integration test and the testing swiss-army knife. Josh Long will personally visit us if we delete (or disable) this autogenerated test. There’s literally no excuse to write no test - except we don’t know the how (yet). But this we can easily fix. There’s plenty of hands-on testing advice available on this blog. Start with the following Spring Boot unit and integration testing overview. Next, consider enrolling for the Testing Spring Boot Applications Primer to kickstart your Spring Boot testing success. Testing Pitfall 4: Not Reusing the Spring TestContext This is coupled to the second pitfall (Extensive usage of @SpringBootTest). Starting a new Spring TestContext for each and every test class is expensive. So why not cache an already started Spring TestContext? That’s exactly what Spring Test does for us! Whenever we’re about to start a new Spring Test- Context, a sliced or the entire context, Spring considers an already started context for this test. If an existing context matches the context configuration for the test class we’re about to run, Spring will reuse the context. If there’s no suitable cache context already started (speak a cache miss), Spring starts a new one and stores the context afterward for further reuse of other tests. So how does Spring determine whether or not a context can be reused and how can we use this feature effectively? Imagine one integration test activates the profile integration-test while another test activates web-test. In such a case, Springwon’t reuse the same context because our configuration looks entirely different due to the different profiles. There are more than ten configuration and setup values that determine the uniqueness of a cache. To effectively use this performance improvement,
📄 Page 11
Spring Boot Testing Demystified 7 we have to align most of our Spring TestContext setups. We should avoid multiple context configurations, especially for tests thatworkwith the entire ApplicationContext. In one of my projects, I reduced the entire build time (running mvn verify) from 25 minutes to 9 minutes while making the most of theSpringTestContextCachingmechanism. I did this by aligning the context configurations for the expensive integration tests. Make yourself familiar with the several configuration values and how to make the most of Spring’s TestContext caching mechanism. Spring Boot Testing Pitfall 5: Mixing Up JUnit 4 and JUnit 5 Another common pitfall when testing Spring Boot applications that leads to weird test results is mixing JUnit 4 and JUnit 5 in the same test class. When answering questions on Stack Overflow, I see a lot of confusion around this topic. While the first version of JUnit 5 was released in 2017, there are still projects out there using the predecessor (which is fine). As JUnit 5 supports running JUnit 4 tests next to JUnit 5 tests,we canmix both for our projects during the migration/transition period. Using annotations and APIs from JUnit 4 and JUnit 5 (JUnit Jupiter, to be precise) won’t work. It’s either-or. While we can have both JUnit 4 and JUnit 5 tests in our project, thanks to the JUnit Vintage engine, one test class should either opt-in of version 4 or 5. Using JUnit 4 for MyOrderTest and JUnit 5 for MyPricingServiceTest is totally fine. The pitfall lies inmixingAPIs and annotations of both versions within the same test. There are tools and guides available to start the migration and help convert the low-hanging fruits. There’s still some manual effort left when it comes to migrating custom Runner or Rule classes. Once the migration to JUnit 5 is done, I recommend excluding any JUnit 4 dependency from the project. This helps to identify JUnit 4 leftovers due to a failing compile step. It also reduces the likelihood to (accidentally) re-introduce JUnit 4 for a new test.
📄 Page 12
Spring Boot Testing Demystified 8 Tips & Tricks Correct Use Of Your Build Tool: Maven Starting with a new programming language is always exciting. However, it can be overwhelming as we have to get comfortable with the language, the tools, conventions, and the general development workflow. This holds true for both developing and testing our applications. When testing Java applications with Maven, there are several concepts and conventions to understand: Maven lifecycles, build phases, plugins, etc. With this section post, we’ll cover the basic concepts for you to understand how testing Java applications with Maven works. What Do We Need Maven For? When writing applications with Java, we can’t just pass our .java files to the JVM (Java Virtual Machine) to run our program. We first have to compile our Java source code to bytecode (.class files) using the Java Compiler (javac). Next, we pass this bytecode to the JVM (java binary on our machines) which then interprets ourprogramand/or compiles parts of it even further tonative machine code. Given this two-step process, someone has to compile our Java classes and package our application accordingly. Manually calling javac and passing the correct classpath is a cumbersome task. A build tool automates this process. As developers, we then only have to execute one command, and everything gets build automatically. The two most adopted build tools for the Java ecosystem are Maven and Gradle. Ancient devs might still prefer Ant, while latest-greatest devs might advocate for Bazel as a build tool for their Java applications. We’re going to focus on Maven with this section. To build and test our Java applications, we need a JDK (Java Development Kit) installed on our machine and Maven.
📄 Page 13
Spring Boot Testing Demystified 9 We can either install Maven as a command-line tool (i.e., place the Maven binary on our system’s PATH) or use the portable Maven Wrapper. The Maven Wrapper is a convenient way to work with Maven without having to install it locally. It allows us to conveniently build Java projects with Maven without having to install and configure Maven as a CLI tool on our machine When creating a new Spring Boot project, for example, you might have already wondered what the mvnw and mvnw.cmd files inside the root of the project are used for. That’s the Maven Wrapper (the idea is borrowed from Gradle). Creating a New Maven Project There are several ways to bootstrap a new Maven project. Most of the pop- ular Java application frameworks offer a project bootstrapping wizard-like interface. Good examples are the Spring Initializr for new Spring Boot appli- cations, Quarkus, MicroProfile. If we want to create a new Maven project without any framework support, we can use a Maven Archetype to create new projects. These archetypes are a project templating toolkit to generate a new Maven project conveniently. Maven provides a set of default Archetypes artifacts for several purposes like a new web app, a new Maven plugin project, or a simple quickstart project. We bootstrap a new Java project from one of these Archetypes using the mvn command-line tool: mvn archetype:generate \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.4 \ -DgroupId=com.mycompany \ -DartifactId=order-service The skeletonprojectswe createwith the officialMavenArchetypes are a good place to start. However, some of these archetypes generate projects with outdated dependency versions like JUnit 4.11.
📄 Page 14
Spring Boot Testing Demystified 10 While it’s not a big effort to manually bump the dependency version after the project initialization, having an up-to-dateMaven Archetype in the first place is even better. Minimal Maven Project For Testing Java Applications As part of my Custom Maven Archetype open-source project on GitHub, I’ve published a collection of useful Maven Archetypes. One of them is the java-testing-toolkit to create a Java Maven project with basic testing capabili- ties. Creating our own Maven Archetype is almost no effort. We can create a new testing playground project using this customMaven Archetype with the following Maven command (for Linux & Mac): mvn archetype:generate \ -DarchetypeGroupId=de.rieckpil.archetypes \ -DarchetypeArtifactId=testing-toolkit \ -DarchetypeVersion=1.0.0 \ -DgroupId=com.mycompany \ -DartifactId=order-service ForWindows (bothPowerShell andCMD),we canuse the following command to bootstrap a new project from this template: mvn archetype:generate "-DarchetypeGroupId=de.rieckpil.archetypes" "-DarchetypeArtifactId=t\ esting-toolkit" "-DarchetypeVersion=1.0.0" "-DgroupId=com.mycompany" "-DartifactId=order-se\ rvice" "-DinteractiveMode=false" We can adjust both -DgroupId and -DartifactId to our project’s or company’s preference. The generated project comes with a basic set of the most central Java testing libraries. We can use it as a blueprint for our next project or explore testing Java applications with this playground. In summary, the following default configuration and libraries are part of this project shell: • Java 11 • JUnit Jupiter, Mockito, and Testcontainers dependencies • A basic unit test
📄 Page 15
Spring Boot Testing Demystified 11 • Maven Surefire and Failsafe Plugin configuration • A basic .gitignore • Maven Wrapper Next, we have to ensure a JDK 11 (or higher) is on our PATH and also JAVA_HOME points to the installation folder: $ java -version openjdk version "11.0.10" 2021-01-19 OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.10+9) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.10+9, mixed mode) # Windows $ echo %JAVA_HOME% C:\Program Files\AdoptOpenJDK\jdk-11.0.10.9-hotspot # Mac and Linux $ echo $JAVA_HOME /usr/lib/jvm/adoptopenjdk-11.0.10.9-hotspot As a final verification step,we cannowbuild and test this projectwithMaven: $ mvn archetype:generate ... // generate the project $ cd order-service // navigate into the folder $ ./mvnw package // mvnw.cmd package for Windows .... [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.326 s [INFO] Finished at: 2021-06-03T08:31:11+02:00 [INFO] ------------------------------------------------------------------------ We can now open and import the project to our editor or IDE (IntelliJ IDEA, Eclipse, NetBeans, Visual Code, etc.) to inspect the generated project inmore detail. Important Testing Folders and Files First, let’s take a look at the folders and files that are relevant for testing Java applications with Maven:
📄 Page 16
Spring Boot Testing Demystified 12 • src/test/java This folder is the main place to add our Java test classes (.java files). As a general recommendation, we should try to mirror the package structure of our production code (src/main/java). Especially if there’s a direct relationship between the test and a source class. The corresponding CustomerServiceTest for a CustomerService class inside the package com.company.customer should be placed in the same package within src/test/java. This improves the likelihood that our colleagues (and our future us) locate the corresponding test for a particular Java class without toomany facepalms. Most of the IDEs and editors provide further support to jump to a test class. IntelliJ IDEA, for example, provides a shortcut (Ctrl+ Shift + T) to navigate from a source file to its test classes(s) and vice-versa. • src/test/resources As part of this folder, we store static files that are only relevant for our test. This might be a CSV file to import test customers for an integration test, a dummy JSON response for testing our HTTP clients, or a configuration file. • target/test-classes At this location, Maven places our compiled test classes (.class files) and test resources whenever the Maven compiler compiles our test sources. We can explicitly trigger this with mvn test-compile and add a clean if we want to remove the existing content of the entire target folder first. Usually, there’s no need to perform any manual operations inside this folder as it contains build artifacts. Nevertheless, it’s helpful to investigate the content for this folder whenever we face test failures because we, e.g., can’t read a file from the classpath.
📄 Page 17
Spring Boot Testing Demystified 13 Taking a look at this folder (after the Maven compiler did its work), can help understanding where a resources file ended up on the classpath. • pom.xml This is the heart of our Maven project. The abbreviation stands for Project Object Model. Within this file, we define metadata about our project (e.g., description, artifactId, developers, etc.),whichdependencieswe require, and the configuration of our plugins. Maven and Java Testing Naming Conventions Next, let’s take a look at the naming conventions for our test classes. We can separate our tests into two (or even more) basic categories: unit and integration test. To distinguish the tests for both of these two categories, we use different naming conventions with Maven. The Maven Surefire Plugin, more about this plugin later, is designed to run our unit tests. The following patterns are the defaults so that the plugin will detect a class as a test: • **/Test*.java • **/*Test.java • **/*Tests.java • **/*TestCase.java Sowhat’s actually a unit test? Several smart people came upwith a definition for this term. One of such smart people isMichael Feathers. He’s turning the definition around and defines what a unit test is not: A test is not a unit test if it … * talks to the database * communicates across the network * touches the file system
📄 Page 18
Spring Boot Testing Demystified 14 * can’t run at the same time as any of your other unit tests * or you have to do special things to your environment (such as editing config files) to run it. KevlinHenney is also a great source of inspiration for a definition of the term unit test. Nevertheless, our own definition or the definition of our coworkers might be entirely different. In the end, the actual definition is secondary as long as we’re sharing the same definition within our team and talking about the same thing when referring to the term unit test. The Maven Failsafe Plugin, designed to run our integration tests, detects our integration tests by the following default patterns: • **/IT*.java • **/*IT.java • **/*ITCase.java We can also override the default patterns for both plugins and come up with a different naming convention. However, sticking to the defaults is recommended. When Are Our Java Tests Executed? Maven is built around the concept of build lifecycles. There are three built-in lifecycles: • default: handling project building and deployment • clean: project cleaning • site: the creation of our project’s (documentation) site Each of the three built-in lifecycles has a list of build phases. For our testing example, the default lifecycle is important. The default lifecycle compromises
📄 Page 19
Spring Boot Testing Demystified 15 a set of build phases to handle building, testing, and deploying our Java project. Each phase represents a stage in the build lifecycle with a central responsibility: TODO: Image In short, the several phases have the following responsibilities: • validate: validate that our project setup is correct (e.g., we have the correct Maven folder structure) • compile: compile our source code with javac • test: run our unit tests • package: build our project in its distributable format (e.g., JAR or WAR) • verify: run our integration tests and further checks (e.g., the OWASP dependency check) • install: install the distributable format into our local repository (∼/.m2 folder) • deploy: deploy the project to a remote repository (e.g., Maven Central or a company hosted Nexus Repository/Artifactory) These build phases represent the central phases of the default lifecycle. There are actually more phases. For a complete list, please refer to the Lifecycle Reference of the officialMaven documentation.Wheneverwe execute a build phase, our project will go through all build phases and sequentially until the build phase we specified. To phrase it differently, when we run mvn package, for example, Maven will execute the default lifecycle phases up to package in order: validate -> compile -> test -> package If one of the build phases in the chain fails, the entire build process will terminate. Imagine our Java source code has amissing semicolon, the compile phase would detect this and terminate the process. As with a corrupt source file, there’ll be no compiled .class file to test.
📄 Page 20
Spring Boot Testing Demystified 16 When it comes to testing our Javaproject, both the test and verifybuild phases are of importance. As part of the test phase, we’re running our unit testswith the Maven Surefire Plugin, and with verify our integration tests are executed by the Maven Failsafe Plugin. Let’s take a look at these two plugins. Running Unit Tests With the Maven Surefire Plugin TheMaven Surefire is responsible for running our unit tests. Wemust either follow the default naming convention of our test classes, as discussed above, or configure a different pattern thatmatches our customnaming convention. In both cases, we have to place our tests inside src/test/java folder for the plugin to pick themup. For the upcoming examples,we’re using a basic format method public class Main { public String format(String input) { return input.toUpperCase(); } } … and its corresponding test as a unit test blueprint: import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class MainTest { private Main cut; @BeforeEach void setUp() { this.cut = new Main(); } @Test void shouldReturnFormattedUppercase() { String input = "duke"; String result = cut.format(input);
The above is a preview of the first 20 pages. Register to read the complete e-book.

💝 Support Author

0.00
Total Amount (¥)
0
Donation Count

Login to support the author

Login Now
Back to List