About this talk
This talk explores the intricacies of testing Spring Boot applications, positioning it as an essential practice in modern software development. The speaker emphasizes the growing importance of automated testing, particularly in the context of increased code generation by AI tools. Acknowledging the pitfalls of unit testing, the speaker introduces concepts like slice testing—focusing on particular layers of an application— and integration testing, which is crucial for ensuring the application functions cohesively with various dependencies. Additionally, the talk covers practical strategies for optimizing test execution on CI/CD pipelines, such as context caching and parallelization. By leveraging Spring Boot's built-in capabilities, developers can improve the reliability and speed of their test suites while fostering a culture of testing within their teams.
Full transcript
[music] >> So, welcome everyone to my talk testing Spring Boot applications demystified. This is the last session for this slot at least. So, I guess afterwards there will be one more talk. So, I really appreciate you coming on the last conference day to uh such a boring topic um talking about automated testing. I tried to make it a bit fun. So, we are going through the hero's
journey through the Spring Boot testing labyrinth. There are a lot of slides, a lot of content. We only have 45 minutes. So, I try to give you as much value out of this maybe last session at least for this track. I want to make it a little bit more interactive. So, I'm aware that we have Slido, but for this talk please um check this QR code. We
are using Mentee. This is an anonymous polling and Q&A tool where I've prepared five to six questions. You can still use Slido or Mentee for a FAQ afterwards. But this tool allows me to get a little bit more insights into into your testing habits. So, the first two questions you will find on this Mentee are despite having LLMs and code agents, do you still write your automated
tests by hand? And the second question is do you actually enjoy writing these tests? So, if you can spend some time scanning this QR code and if the technology works, we can already see the live responses from the audience. So, we will see here good amount of people still write them by hand the automated tests which is great. Do you enjoy writing automated tests? So, there's also
a big tendency towards yes, but we at least have three people that don't enjoy it at all. So, I try to make it a little bit more enjoyable with my talk. There will be more questions or more polls Uh, in the Mentee. You can already answer them now or later. But, we can get started. So, why bother at all with with testing software? Can't we just, uh,
write beautiful code and ship it to production and uh, live happily after? No, we can't, especially these days. So, what I'm seeing with all the AI coding tools, with all cloud code and codex, testing, will become or is potentially a superpower. So, the productivity AI trap that you will read in the headlines is everyone is getting more productive, but if we take a look at the actual
results, teams are producing more code, but making sure this code is maintainable, is fast, is testable is super hard. For those of you who have been on call, um, the problem is even worse now. So, if AI generates the code, you as developer still own the liability. So, if the the shitty wipe coded code gets into production, you are still the person that gets called at night
and has to fix the bug. So, there there is uh, a potential issue if we don't invest much time into automated testing. I see it like AI is an accelerator, which is a great tool, but we need at least automated testing as a break to have this car analogy. We can't just accelerate, we also need to to have some brakes or some guardrails. So, I believe automated
testing is more important than ever. I'm working as a consultant and when teams ask me how much testing do we need, I stopped answering uh, any code coverage goals because any KPI you track uh, will become a vanity metric and developers will find ways around. So, what I started to to tell people and is my overall North Star that I have not only with automated testing, but
with all the project or our that I work with. So, the idea is uh, imagine you get a pull request notification from Renovate or Dependabot on a Friday afternoon. It tells you to bump to Spring Boot 4. The pipeline is green. You could squash and merge. And now the question is how confident are you and your team to click squash and merge on a Friday afternoon, bring
this live to production immediately, and go to a yeah, relaxing weekend with your family. So, that's more or less quite strict or hard to achieve North Star, but at least for the teams that I've worked with that performed continuous live deployment, TDD, all the good patterns, this was what they were all optimizing for. So, for your team, it might look different. You might have a release cadence.
You can't do this, but all the automation effort, testing, feature flags, improving build pipelines, all of these activities yeah, play towards this goal. And this is what I usually take as a as an answer when we in the team decide is it now time for the first canary end-to-end testing on production, or should we rather tackle test parallelization of unit tests? So, this gives me at least
a good indicator on what to invest in. So, in short, for me good testing is not just catching bugs. It's like having fast feedback and confident deployments that we as developers can can iterate as fast as possible and get as early as possible the feedback from our test suite and on all the surrounding automation we have. So, how to get there? Um at least what I will
show you today, that's all about Spring Boot. That's the agenda of our talk. So, we stay in this labyrinth metaphor. Together we will enter the labyrinth. We will fight three bosses. So, we will fight the Swift Gatekeeper, which is all about unit testing. We will take a look at the multi-headed hydra. This is all about slice testing, a special concept that Spring Boot introduced. And last but
not least, we will also tackle integration testing as the the final boss for it. So, it's not all about writing the test. They also have to execute fast and are reliable. So, that's why we will also collect three quest items along the way. You can see them in the middle of the journey. This is first the caching amulet, the lightning shield, and the scroll of truth, which
will all help you to write an even better test suite. So, that's for the agenda. Goals for this talk, so I'm well aware within 45 minutes I can't give you all the good things, but at least I want to provide you a clear mental map of of what Spring Boot provides, what are the convenient defaults that we can use. And I want to show you practical examples,
so there is no benefit if I show you how to write a unit test for a calculator and tomorrow you go back to your real-world project, you have to tackle Keycloak, Kafka, whatever. So, I try to be as realistic as possible so that in the end maybe ship code with a bit more confidence. Some words about me, so I'm Philip. I'm self-employed developer from a small town
called Herzogenaurach, close to Erlangen and Nuremberg. Um I've been blogging for quite some years and really doubled down on this niche to create content, to do talks, to do workshops on Spring Boot testing almost 5 years ago. Founded a company to to help clients and these days even enjoy writing tests even more than production code. So, that's also a funny outcome if you spend so much time
in source test Java that it becomes more more funny to to yeah, break the software you're writing. So, let's get started. Um we're at the front of the entrance of the labyrinth. Uh imagine you're a Spring Boot newcomer, new to Java, new to Spring Boot. You're overwhelmed with all the annotations. You're overwhelmed with um the auto configuration, the magic, the convenient defaults. You have no clue what's
going on. And then you have to tackle automated testing because your team lead uh tells you to do so, or you check the uh definition of done, which requires at least some tests. So, what we do as developers, we ask AI, or back then ask Stack Overflow, uh how to do it. We potentially blindly sleep copy-paste uh stuff from the internet hoping it works. In the beginning,
it's quite hard to get tests to to be green when they involve an application context. You see failed context launches. You don't understand Then there is even more annotations you have to learn when to use, how to use, which tests are they targeting. So, it's quite complex. So, it might feel like you're blindfolded in the labyrinth. So, that's at least how it was for me as a
newcomer for Spring And we want to fix this. So, when we enter now this this labyrinth, um we immediately have to tackle the first uh boss, which is which is you the unit testing guardian. Blocks those who overcomplicate. So, before we tackle unit testing, we first have to tackle tooling. So, with tooling, Spring Boot, uh lucky us, has our backs. So, there is what I call the
testing Swiss Army knife uh as part of all of our Spring Boot projects. So, you see this five lines of XML code that all your uh Spring Boot projects will have. This Spring Boot starter test brings not only Spring Boot testing features, but also a set of Java testing tools, assertion libraries, and much more out of the box, batteries included from day one. Spring Boot will take
care to manage the library versions for us, and we can get started really on on on on the first day. We don't have to argue with our architect, do we use TestNG with assertion libraries, do we use how do we configure Maven, or Gradle. So, super convenience for us, Spring Boot has our back for the tooling. To understand what gets included, we can check the transitive dependencies
that this Spring Boot starter pulls. What we will see is a lot of output. Most important, we see some Spring Boot starters. We We see all those highlighted things here. Those are additional testing libraries that get pulled. We don't have to specify versions. So, Spring Boot by default, the Spring Boot parent POM, similar for like your JDBC driver, takes care to make to make a convenient guess
what version to use. It bumps it regularly, ensures there's compatibility. So, all of the things we don't have to care, and we can use these tools. So, what's included? Uh first and most important is JUnit. So, if we are working with Spring Boot 4, we get JUnit 6, and everything before is either JUnit 5 or previous version. JUnit, uh Java's de facto standard um testing framework. The
famous at test annotation, assert equals, but there is even more, the extension API, test parallelization, test factories, and much more. So, this is like the baseline of what we need. We get Mockito mocking library, AssertJ, Hamcrest, two assertion libraries. If we don't like this assert equals and wanted to have a bit more fluent, then we get this assert that. All of this is included inside our project.
When working with JSON data, just comparing text strings is super hard. So, for to solve this, there's JSON assert. There we can compare JSON strings with with each other. And if we have bigger nested JSON objects, for example, and we are only interested in a sub part of the JSON, we get JSON path, which is like XPath for XML. This allows us to extract, yeah, with an
extraction language of a JSON object that we can then afterwards verify. There's XMLUnit for those working with XML. And this one was added was added as the last one, not recently, but some years ago, Awaitility. This is a really nice niche library that helps you test asynchronous code. So, imagine you have a message listener that reads a message and puts an element into a database. If you
write an integration test and would immediately write the assertion after putting the message in the queue, you will get a flaky test because this process happens asynchronously and you can't immediately assert that something is stored in the database. You might help yourself with thread.sleep, but if you repeat this pattern for your entire test suite, you lose quite some traction because the feedback you're getting is is worse.
Awaitility fixes this. You have a fluent way of expressing how long to wait for a particular thing to to to happen, to an assertion, basically. So, all of this is included. Could spend at least for each library 1 hour to explain it in detail, but for now, just to be aware what's in your toolbox, like this you can use from day When we test tackle now unit
testing, the good news is you can use all your ideas, patterns that you have learned in other programming languages or other frameworks. So, unit testing Java code as a concept there is no difference to any other programming language. So, the idea is you test a small component, usually a public method in isolation. And yeah, test its behavior, not implementation. The confidence you gain, you have yeah, fast
tests that yeah, test some business logic in quite There are some pitfalls with unit testing. So, I guess we have all seen these classes like a Java class with 400 lines of code injecting 10 to 15 beans using the public constructor, public methods that clearly violate the single responsibility um yeah, pattern. unit testing is super hard there, but this is also a early smell for your potential
optimization in your class design. So, whenever unit testing feels hard, maybe uh the tests we're just giving you a hint to to better design your classes. The tools you are using, obviously a test framework um Mockito, if you if you favor Mockito or not, and an assertion library. So, that's basically unit testing. There is much more to to tackle, but this is like the uh or at
least how I define unit testing for Java Spring Boot applications. If we now consider a potential class that we want to unit test, so imagine this controller. A classic uh REST API HTTP put, uh HTTP post, sorry, to create a new entity. We take uh the payload as part of our public constructor. This will be JSON serialized by by Spring for us. We delegate creation of the
object uh to a service, and then we get the ID returned. And then to follow RESTful best practices, we are returning HTTP 201 with the location header, so the potential client could re-query. So, basic Spring Boot controller example with only the knowledge I've shared for a developer might be tempted to start writing a plain old unit test with only using JUnit and Mockito. The potential unit test
might look something similar like this. So, somehow we activate the Mockito extension, we declare the class we are dependent on as a mock because we don't want its actual behavior. We want to to be in the driver's seat to override the the customer service. We use inject mocks to create an an instance of customer controller and Mockito will inject the customer And then a basic unit test,
yeah, some Mockito stubbing, then we will call the controller. Feels already a bit brute force. So, you're calling the public method of a controller. During runtime, this would be like there are a lot of filters in front. Yeah, let's see. So, we can do it. It's a public method. It had It has a return type. So, we return the famous response And lucky us, we can then
write some assertion on this response entity to check the status code quite statically and to check, for example, the location header. >> [snorts] >> So, that's possible. One might argue, what's the benefit of writing a unit test for these, I don't know, seven lines of implementation? There is no if else. What should we actually verify? Aren't we not on the boundary to the framework? Do we test
the framework? What's happening? So, this unit test seems valid, but there are some issues. So, this unit test won't give us the best safety net we could get because this unit test has various problems. Imagine we during runtime we exposed this controller endpoint to a specific path. And with this path, we have a contract to another team, for example. So, we should be making sure with an
automated test that this mapping is in place, and not by a Spring Boot change, someone figuring out that there is a servlet context prefix I can use, that this that this path will change, and we potentially uh yeah, have issues in production because our clients can't find us anymore. So, a unit test would not catch a change to the path, or wouldn't help us making sure the
um path variable extraction, query parameters, all the complex things that we might do would work. Because if we change in the post mapping, the endpoint, unit test is still green because uh it yeah, uses the public method to call it. Nothing is matched. So, that's the first issue. There's another issue. We I haven't showed this much in detail, but we are using bean validation. Uh with bean
validation, we want to ensure that we return HTTP 400 when there is uh yeah, wrong data sent to our backend. With a plain old unit test, we can instantiate any arbitrary Java object, which wouldn't uh ensure that bean validation is is triggered. So, you see here, I do expect an actual email. So, I'm using at email uh from Jakarta validation on the on the on the record.
With the unit test, I can pass any arbitrary string. So, that's not caught. Then, all about serialization. We can't test serialization. Imagine um someone yeah, changes hyphen to underscore, camel case to kebab case, all the things. These are like hard contracts we have with our clients or customers that can't be covered with a unit test. So, if you are inside IntelliJ, you do your refactorings, you rename
your field names on the return to DTO or the accepted the the unit test is still green because the public constructor doesn't care. There is no no verification on the the fields. So, also here we lack out some confidence that we could potentially get. And the last issue is all about Spring Security. Brute force calling the public method Yeah. wouldn't care about Spring Security rules or potential
authorization and authentication rules. So, also this we can't test with a unit test. And for sure, unit test is also not designed to to verify it here. But, I want to show you this example to understand we need something else, at least for some parts of our application. And that's slice testing. To understand slice testing, um let's take this as a sample Spring Boot application. A small
microservice. We have a REST API and a front end connects to. We do store data in a persistent storage. We have network communication to other teams. And the big circle you're seeing is our entire application context during runtime. The small circles you're seeing are all the Spring singleton beans that make up our con- context. So, all your services, your entities, your repositories, HTTP clients, the entity manager,
all the technical things make up your application during runtime. What we can do in the next slide, I've colored them. So, we can color them by technical domain. So, for example, everything that is green here is related to the persistence domain. So, for example, the entity manager, the data source, transaction manager, entity, repository. All this seems to be related. And we can apply this pattern to other
parts. So, blue is all the web layer. Red is, for example, HTTP client layerish. And what whatever is yellow is the rest, the services, some arbitrary technical When doing this, um or what the Spring to Spring Boot team did now as a smart move, they started to color those beans, like hypothetically, and group them together to slice the context. Because when we proceed with our testing efforts
now, imagine our use case, we want to verify bean validation is in place, authorization rules are in place. When we test these cross-cutting concerns, we are not interested if we are connected to Cassandra, if there's Kafka going on. We want to test this in isolation, and Spring Boot helps us to create a minimal sliced application context for testing purposes for us. And it does this by these
various Spring Boot test slice annotations. So, we will take a quick look at the web layer. There are annotations for all web layer technologies. So, whether you are using GraphQL, reactive, or the classic you get an annotation that will create a minimal application context. So, not not the entire application context, but just a slice with all the technical necessary beans for you to test web layer specific
concerns way better and with much more confidence than just a plain old unit test. So, the way this works with Web MVC test, um we use the annotation, you see this circle we are getting is much smaller. So, Spring includes only relevant beans. Technically, um there is no time to show this, but Spring has like a shopping list. So, I compare it like a fractional component scan
it does. So, during runtime, it would do an entire component scan, put everything in the context for the slice testing. It has like a shopping list. It knows all the technical beans it needs to collect and populate and put into the context to give it the user, which is us, context that is just minimal. So, for sure a repository doesn't fit in there. The data source doesn't
fit in there because web layer, we don't care. So, Spring Boot does this behind the scenes and we get a small context. One issue, whenever we have a dependency to another technical layer, at the beginning we get a context failure because the rest controller, like you saw in my example, depends on the service. And the service is not in this shopping list for the web layer because
service Yeah, has no impact on the web layer. So, we as developers have to fix this. The most uh pragmatic approach is to use Mockito bean or mock bean to provide a mocked version of the service in this minimal application context. So, the controller is happy because uh it it requires a service and then we get our controller some technical web beans around. Everything starting really fast.
So, also there's quite some benefit because comparing starting just a minimal context, instantiating all the beans, it's it's much cheaper than starting the entire application context. So, that's what's happening. There's unfortunately no time to show this in detail, but the good news is um we get then also a mocked servlet environment. This mock MVC is both an environment and the client and this allows us to test
the HTTP semantics. We can then fire HTTP-like requests. So, there is no port occupied. There is no Tomcat started, but everything is in memory and you can test much more things. It nicely integrates with Spring Security. So, super cool way for our controller. There are much more test slices. So, usually I start with a controller. We could also look at the data layer. So, there is data
JPA test, JDBC test. Imagine you have a native SQL query on your repository. What do you want to test with a unit test? That's impossible. You you need somehow your database. So, the the slice tests for include all your entities, transaction manager, data source, and all the other technical things that make up the persistence layer configured out of the box by Spring Boot for us. And much
more niche, even more of these are available. The documentation is excellent. Uh check out the Spring Boot docs if you want to see if there is some for your persistence technology. For example, there is Redis, Cassandra, Couchbase, So, usually if you use something more famous persistence technology, you should be you should be good. Summarizing this now, so we we we we left unit testing. Now we are
testing with an application context. We don't test with the entire application context. We test with only with a slice. The confidence we gain, we can now test, for example, the outer parts. Usually for the outer parts of our application, slice testing makes a lot of sense. So, message listeners, HTTP clients, persistence stuff, web layer is much better tested, or at least the confidence we gain in the
test is is way better. Usually web layer is the place to start and the easiest to learn. There are some pitfalls. For sure, you now need a bit of Spring Boot magic knowledge. So, you need to understand how this component scan works. You need to understand what's part of the shopping list, what's included, what do I have to provide into the context. But the good news is,
once you write your first slice test for your web layer for your project, you can apply this pattern for all other controllers, making it super simple. This brings us to the last boss, integration testing. I hope most of you are aware of this famous um test that all your projects have. It's super small. It has a new annotation at Spring Boot test and a single test that
is empty called context loads. What this will do, it will try to start the entire application context and as the test method name applies, try to see if everything can be wired together. This test is usually green as long as your project doesn't depend on any infrastructure resource. As long as you have a more serious project like this example, you connect to a database, you connect to
message listeners, maybe a blob storage or you do network calls, this test will fail and we have to tackle various problems. So, what are the problems we have to now tackle in integration testing when we start the entire application context? So, first we have to ensure surrounding infrastructure is present. So, I guess we shouldn't tell our developers with a two-page PDF on Confluence to download Postgres locally,
to download the message queue locally, set up Keycloak in this way. This is a bit There's friction involved and we want to also automate this on CI. The next one is how do we interact with our application now? And the last problem, how do we keep the build now at an actual reasonable duration because it's not just all about writing the tests. They also have to execute
fast, otherwise we might be tempted to skip them locally, only run them at night, which makes matters worse because we want to fast feedback as early as possible. And there's even more which we don't cover, but imagine like what do we do with HTTP communication during integration tests? Like the problem what I want to show you here, it becomes more complex. It's not like trivial to get
this up for a mid-size Spring Boot application. How do we handle test data, maintain a clean test state between the tests? How do we handle authentication? So, a lot of things to to learn to tackle to write meaningful integration test. We'll just take a look at the first three and to solve problem number one, we will introduce test containers. So, test containers still one of the game-changing
uh Java testing libraries of the last years. In short, test containers is like a wrapper on top of the uh Docker Java API to manage the life cycle of a Docker container during tests. So, it will ensure to pull the image, start the container, let the test wait until the container is started and ready to accept traffic, then run the test, afterwards clean up the container, and
make sure uh our our machine is is is clean. So, good news is like we can use this pattern now to start everything that we can containerize with Docker with test containers for integration test purposes. What you see here with these seven lines, it's all the magic you need to start Postgres at a specific version locally for your integration test. And then with this pattern, we can
get as close to production as possible. We can use the exact same uh database version, the exact same setup. For sure not the machine size, production still looks different when it comes to to payload or the the the RAM of of your database engine, but still better than an H2 uh in-memory database or or any hash map that we use uh to to fake the database It's
a bit small here, but if you're new to test containers, test containers also starts the container on a few more ports. There are no port clashes, which makes it more complex because we get a a random dynamic JDBC URL, so to say, on each test invocation because the port will change. There is There are multiple ways to now tell our test context to connect to exactly this.
There is @ServiceConnection. This is a new Spring Boot annotation that does all the magic behind the scenes, but you will find on the internet a lot of resources how to connect uh your context to to anything dynamic, but that's basically the pattern you need and you can replicate for a message listener, for an IO blob storage, or whatever you Answering problem number two, how do we now
access our application? So, that's important. There are basically two options. If we use @SpringBootTest, like you saw in the in one of the first slides, what will happen? Spring Boot will start the entire application context, put everything inside, but will not start the servlet container. So, there won't be a port, there won't be Tomcat started. It will just be the entire application context. What you usually do,
you then tell it at this option to also configure Mock MVC, so you get this mocked environment for the servlet API, as well as your entire application context, and could test your application like end-to-end almost. Option number two, if you really want Tomcat to be started, which you should because, again, you want to be as close to production as possible, the Mock MVC is great, but still
there is a difference to compared to your Tomcat on production. To actually trigger Tomcat to be started, you need to override the web environment to either random port or defined port, and only then, uh, will the, uh, servlet container. You are then running again on a local ephemeral port. There are many test clients by Spring that are then auto-configured, and you can then test your application with
the public API, and test all your use cases. the pattern, but most importantly, be aware there's a huge difference, uh, between option one and option two. Also, with transaction handling during test, with security, cleanup, um, yeah. Be aware of what you're using there, but basically that's the idea. Bringing us to the last topic um problem number three. How do we now keep our build at a reasonable
duration? Um this brings me to I guess one of the most hidden gems when it comes to Spring Boot testing. Um this is what I call the caching amulet. The idea is um every integration test or every slice test starts its own application context. This is expensive. This can take up to I don't know a minute depending on a legacy project with not so optimal hardware, starting
everything, starting test containers. This takes time. The solution that Spring Boot or the Spring Test team provided is um they implemented a caching mechanism. This is already part of all your projects. You might not be aware of it. So, in the behind the scenes, Spring Test uh tries to cache as much as possible. This can result in huge um performance benefits or to reduce your build times.
What you will see here, this was for one of my clients. We reduced the entire build time. So, Maven verify on GitHub Actions taking 26 Only optimizing this feature, we we brought the build time down to 12 minutes. So, we didn't even start parallelizing. We didn't invest in more beefy agents. It that's only all about context caching. And the way it works is like any other cache,
behind the scenes, Spring has a cache. The cache has a key and a value. Let's take a look at this artificial example. We have three integration tests running in order in sequence. Order IT, Payment IT, Checkout IT. The first test that starts sees an empty cache. So, it has to start the application context. You will see here three and a half seconds taking quite some time. After
the tests, because the the lookup was uh bad, it will put its application context, that's this small fruit salad there, with an unique identifier into a cache. When the next test runs, in our example, it has the exact same cache key. We will see how this cache key is built. The test will be super fast because it will do a look up, see there's already an application
context started, and Spring will just hand it over to the test, making the test execution super fast. You see here 400 milliseconds, uh much faster than the first example because it doesn't have to boot the The last test, unfortunately, um has a new uh fruit salad configuration, so to say, and yeah, has to start the context itself, and afterwards puts the context into the cache, giving us
in the end three integration tests that were executed. One was super fast, two were slow because they had to start the context, and the cache looked like this in the end, two elements in the cache that could be potentially reused in the future. So, how is this cache key built? Because with any cache, we want to have as many cache hits as possible. So, this identifier I
showed you, we should optimize our test suite to yeah, match for most of our integration tests. What you see on top here is Spring test source code. So, that's actually how this cache is implemented. It's a classic linked hash map, 32 elements of max size. We can tweak this, increase or decrease. The key is a Spring test class called merged context configuration, and the value is the
actual application context, so the entire application, so to say. What Spring will do, so Spring will create an instance of this merged context configuration for all your integration test classes. It has to do like an X-ray on what we are requiring for the integration context customization. So, there are many customization points that we can use as a developer to customize Famous example is we could activate profiles.
So, if we activate profiles, this is one of the customization points. Imagine first integration test activates profile A and the other B. Spring can't reuse the context because Spring is not so smart that it looks also in your source code and understands although you're changing the profile, there is nothing happening because for most projects, changing the profile triggers some different configs configuration. So, whenever there's a slight
difference, um Spring will identify this. There are like 10 technical places Spring will take a look to understand what's the unique context configuration you are asking for. And only if it looks exactly the same as for one of the tests that I executed before, only then I can hand over the the context for you. So, in schema, it looks like this. The test class runs. Spring uh
creates an instance of this object, collects all these X-ray customization points, gathers them in this Java object, and we have a Java object, we have a hash code and equals, and this will then define inside our cache do we have a cache hit, which is good. A cache miss is bad because then we have to restart uh a new context. How do you Oh, so now the
question how you can identify this for your project. Number one, you can visually analyze the log files. So, when you see the first test is slow and the second test is fast, that's an indicator something was reused. You can also watch out for the Spring Boot banner in your logs. Whenever you see the banner, uh you clearly got a new context and there was no reuse happening.
If you want to have it more deterministic, you can activate a log. So, here this trace, run all your tests, take a look at the last log message, and then Spring will show you caching statistics for your um test suite. And as third option, uh that's Spring Test Profiler. That's an open source tool I have written. It's on GitHub, works with Maven, Gradle, and Spring Boot 2
3 4. You plug it inside your uh build, uh you run it, and what what you get as an result is a nice-looking HTML uh that will give you some hints. Also, maybe good for your team lead to argue, is it worth optimizing our build suite further because here you see potential time savings that you could have if you would reuse more application context for your project.
Some statistic, but the most interesting part here is this. This lets you compare two contexts in the cache after running the entire test suite. And then if this topic is new to you, you might wonder what are all these And here you can then click on all the red things to compare two contexts after a test. And you see context A did not activate any profile. Context
B activated profile test. And this is a hint for you to then streamline your context configuration, so at the end they all look the same. There are much more technical customization points, but once you're used to this, you should be good. I mean, also quite obvious, if you change properties, for sure Spring can't reuse it because if properties change, something inside a context will look differently. So,
yeah, this tool should help you and your team to first be aware of this feature because the you get the most of the benefit if all your developers and code code is aware of it so that you can optimize for it and not spawn so many fruit salads throughout your test suite, which slow down your entire build. So, coming to quest item number two, just briefly, uh
the lightning shield, I usually first optimize for this context caching. Once we optimize this, depending on the project, I don't know. There is no target, but it's better to have less context in the cache than more. So, maybe for a mid-size project, five entire application contexts for integration tests. The rest all reuses them. What we focus on next is test parallelization. Easier said than done. So, um
there are some requirements that your tests are parallelizable. So, most importantly, you have to ensure there is no implicit um requirement on the execution order. You will only detect this once you parallelize your tests. The tests also have to be written in a way that they don't share state or they don't mutate between the tests. But, that's something you will learn about once you parallelize. With Java,
we basically have two options for test parallelization. We can parallelize on the build tool. So, this example is Maven Surefire. Same works for failsafe or or the Gradle. We can spawn multiple JVMs for our test suite. So, this is more resource isolated. With having, for example, the downside, if you have two separate JVMs, then there are two context caches. So, if you're not staying within the same
JVM, you can't reuse between the JVMs, at least for now, not. So, but that's possible. It isolates quite nicely. That's an option. The other option we have is we can ask JUnit to parallelize based on a thread pool. Then, we are staying within one JVM and have much more fine-grained configuration properties to then spawn multiple threads that will run and parallelize unit tests, integration tests, or whatever
we have. Both having their pros and cons. Usually, what I do in most projects, we for sure separate unit from integration tests so that we can highly parallelize unit tests because they are in general should be much simpler to parallelize than integration test. And the nice thing is if you use Maven, you can tell Surefire to run unit test and failsafe the integration test, and then you
can tweak separately the parallelization levels. Or then use JUnit Jupiter based on which plugin you are used to override the properties, and yeah, define the parallelization level. There is not much time. Quickly, the last quest item, scroll of truth. This is one of the testing patterns I only recently discovered or used now for a project for a year to challenge code coverage because code coverage for me
is a vanity metric. So, I've seen so many test suites that fake their way up until 80% that there is clearly no benefit in having a code coverage as a tracked KPI if not everybody is on board to write meaningful tests. And to challenge code coverage, what you Imagine this simple implementation. We have some some if statements to check the age. If the person is less than
18 years old, there's an exception. If the username is equal admin, we return a a magic number. What we can do or what there is now this this tool is or is the idea is mutation testing. We can proactively introduce regressions, and then run our test suite to see did the did our test suite detect the regression? So, for example, in our case, remember age less than
equals, it was now changed to not equals, or the magic number was changed from 42 from three to 42. And the tool that you would use with Java is Pit and mutation testing. This tool analyzes your tests, mutates them, creates so-called mutants, and then will run the relevant tests against the mutants. For sure there is only bytecode manipulation, so this won't check in any any changed code
files, but this will give you a much better insight to your potential blind spots or or uh tricks that you or previous developers did to get just the code coverage metric up because these would be then all detected because if we mutate the implementation and the test still pass, you have to wonder is there any benefit in the tests? Do the test test the wrong thing? Do
they assert the wrong things? So, there are yeah, quite some lessons learned afterwards um when using this technology. >> This brings me to the exit. So, um we are now leaving the the the uh the Um I would say Spring Boot or at least the years I've been working with it um comes with excellent and batteries included uh support for for automated testing. For sure, there is
some challenge to learn Spring Boot at the beginning, the auto configuration, to understand slice testing, to make your first integration test running, but once you are past this and see the vast and mature Java testing ecosystem, uh it it usually makes fun and is is super simple. So, we saw these three main quests, unit testing, slice testing, integration testing, and at the end uh we took a
look at context caching, test parallelization, and quickly mutation testing to make our test suite both reliable and fast. >> [sighs and snorts] >> Quick offer at the end. So, um testing or automated testing like development is a team sport, and I guess there is no benefit if only you or you as a team lead now have some more The best outcome you get if this knowledge is
shared across your entire team. So, what I'm offering, I'm offering this talk for free in inside your company. So if you have a lunch and learn session, an internal conference, or I don't know, a good event to host this, please get in touch. I I only need a Zoom link and I will, yeah, onboard also your colleagues. if you want to go even further, so I also
offer workshops. There are two more dates for this year where I'm running my confidence in every commit essentials. So this would be what we cover today in much more detail, hands-on examples that you can follow from your laptop remotely. Take a look at the link. And then last but not least, for those that still stayed, a small gift. So this talk was created based on a book
I wrote with the same title, Testing Spring Boot Applications Demystified. You will get it for nine bucks on Leanpub, but as you stayed so long, I will give it to you for free if you join my newsletter as part of the the last slide. You can download it. Last slide before the last slide, now for final questions. So I would be still here. There is no further
talk. If there is anything to discuss, we can have a look at your questions. Also, feedback is highly appreciated. Please use the J Con schedule to provide any feedback. And with this, and the last QA code to get the ebook for free, I wish you joyful testing. >> [music]
More from this event
See all 34 talks →
Java: 30 Years and Beyond | Ana Maria Mihalceanu (EN)
39:47
Scaling Data in a Sovereign AI Platform | Johann Strauss & Markus Kett (EN)
49:29
Code Is Cheap. Software Isn’t. | Markus Eisele (EN)
47:23
Building a Digital Product Passport with Java and Cardano | Fabian Bormann (EN)
46:54