Java can also be agile — Incremental setup of Spring Boot, Junit5 using Gradle and Java 9 for Agility
(Note: In case you have opened this story in Facebook, the code snippets may not be visible. Please click here to view this story in medium.)
Why I did not like start.spring.io
How I broke down my understanding of start.spring.io
I knew that I could create a project using maven / gradle, and java / kortin / groovy by looking at the UI. I immediately wondered, why not scala? I suppressed my curiosity for the while, having faith that, if I cracked the configuration of springboot using maven or gradle, I should be able to accommodate scala as well. After ruminating over this belief for a few days, I accidentally stumbled across this article by IBM (when I googled for “SpringBoot Basics”). It helped me understand how to easily configure Springboot projects using maven. I enjoyed this knowledge when I started exercising it, to bring in newer versions of the technologies, to soon realise the consequences of my choices. By the way, that article missed out on explaining the source code entirely, which I had to figure out by downloading and comparing my learning project with start.spring.io.
Having gotten used to TDD and BDD, I practically couldn’t live without an intuition of testing frameworks, and I must have the latest features, even if I were not aware of them at the time. I like having the freedom to use new (testing) techniques as I discover them without having to reconfigure my project. Hence, I started to configure junit5 into my project’s pom, to soon realize that I was doing a lot of typing (thanks to pom.xml). In addition to this, I couldn’t get the springboot project (even the one downloaded from start.spring.io), to work with Java9 (“mvn clean package”, was spitting out some errors. maven-jar-plugin incompatibility??).
I prejudicially concluded that the combination of excessive typing and opinionated versions of maven-jar-plugin in the default maven bom did not lend to creating projects by hand, and started to look at gradle, hoping that it did not engrave versions in it’s hidden opinionated layers. I was specifically looking at knowledge that allowed me to build project by using only a simple text editor such as sublime, without any UIs. This video by Tech Learnings helped me to get the necessary basics of gradle in place. Having seen the video, I started working on building intuition, by practising what I learnt.
Configuring a simple java project with Gradle
In the video, I noticed that the folder structure followed for the source code was the same as what I had been using with Maven years back. Experience taught me that the “src/main/java”, allowed me to add new flavours of languages like groovy and scala into the mix (src/main/groovy, src/main/scala), and also keep test cases in a separate folder (src/test/java, src/test/groovy), etc., which turned out to be very useful in the long run. Hence, the first thing I did, was to execute “mkdir -p gradle-learning/src/main/java/in/stackroute/learning”.
I went on to write the very first Hello.java file, with the following text, and was pleased to write it using vim (vim gradle-learning/src/main/java/in/stackroute/learning/Hello.java):
Then, recollecting my learning from the Video, I created the following build.gradle file, also using vim (vim gradle-learning/build.gradle).
Then, I ensured that build and run tasks were both available as a consequence of applying “application” plugin.
I ran the application, to confirm that “Hello, Gradle” printed on the console:
Yippie! I was now curious, where gradle has created the compiled file, and immediately found out:
I also tried to build the jar file for this, and run it, and succeeded in both.
Okay, that was simple and quick. Now I wanted to write a test case using junit5. After the initial misguide caused by blogs and articles (as they did not have any useful information about how to quickly get started unlike the springboot article by IBM), the most relevant information I got was from the official JUnit 5 User Guide. I particularly found sections 3 and 4.2.1 relevant. Section 3 did not waste any time in getting the “Hello World” program out of the way. What I noticed however was the change in the packages of assertEquals and the @Test annotation. Having quickly skimmed through section three, and appreciating the introduction of annotations @BeforeEach, and @AfterEach (as in MochaJS), I made a mental note to revisit Section 3 for my Ninja training in JUnit and proceded to write the test case in my learning project (mkdir -p src/test/java/in/stackroute/learning) (vim src/test/java/in/stackroute/learning/HelloTest.java). Oh yeah, I had to return to the User Guide, to memorise the packages again, before I could write this program using vim.
I do realise that line 10 is not correct. Experience has taught me, that the best way to check whether a testing framework is configured correctly, is to ensure that the test complains about failures, rather than verify success.
Anticipating the program to fail, I run the tests.
I knew I had to include JUnit5 dependencies. This, I found in section 4.2.4, and updated build.gradle file by adding the repository and dependency blocks.
I tried running the Test Case, and this is what I got:
- The test execution is not failing
- The exit code is not non-zero
- A warning is displayed, which doesn’t make sense
As for the third observation, the warning disappears when we run the test again:
Assuming gradle is keeping track of changes in the file, and that tasks are not re-run for performance, I change my focus to problems 1 and 2, which seem logically related. From the User Guide, I understand that junit-platform-gradle-plugin needs to be used, and apply this plugin along with the configuration for adding the dependency in the classpath for the build script. My build.gradle now changed to
And the output of ‘gradle test’ changed to:
Line 13 shows the test failure, and line 45 shows the non-zero error code when the test is run. I also run ‘gradle build’, to confirm that build fails, and the return code is non-zero. Time to fix the test case!
And the output of ‘gradle test’
I quickly run clean, run the test cases, and check whether the test results are stored somewhere, and sure enough, I find a build/test-results directory:
Being overly cautious, I build the jar, run it and confirm that nothing else has gone wrong due to changes in build.gradle. However, the test case still does not access code written in the src/main/java directory. For the sake of completeness, I make the following changes to my Hello.java, HelloTest.java, and confirm that the test case is failing:
And the output:
After this, I change “Griddle!” to “Gradle!” in the test case, and make sure that tests are passing. It’s now time to address the warning (observation 3 above)
A quick google search for the warning text and “gradle” took me to this junit5 issue. To hide the warning, I add the recommended line to my build.gradle file.
I failed the test case, by changing “Gradle!” to “Groddle!”, and confirmed that the warning didn’t show up, before changing it back.
Creating a SpringBoot application
Time to configure a simple SpringBoot application. I tried to find a good article which could explain me how to configure a springboot project using gradle, similar to the IBM’s, but couldn’t find one. I decided to download the skeleton from start.spring.io, and looked at it’s build.gradle, to realise the simplicity of the configuration. This simplicity however, arises due to the similarity in the configuration of JUnit5 as above, and essentially includes two changes to build.gradle.
- Adding dependencies: spring-boot-starter, spring-boot-starter-test
- Applying spring-boot-gradle-plugin
After getting my hands dirty after reading IBM’s article, I understood that any application can be run as a SpringBoot application, by simply calling SpringApplication.run() static method. The drawback of this approach, is that we do not get an über jar.
First, I converted Hello.java, into a SpringBoot application.
I anticipated that the application will not run as before, and got the same compile error, complaining that it couldn’t find the org.springframework package. I then added spring-boot-starter as a compile time dependency, and the SpringBoot application started.
I then tried building the jar file, and tried to run it. This time however, the project did not run, as the spring dependencies were not included in the jar. However, I noticed a “build/distribution” directory, that contained a zip and tar archive of the distribution. The archive contained all the libraries, compiled classes, and two start scripts (shell and bat), one each for *nix and windows. Upon running the shell script, the spring application started as expected.
Having reached to this point, I was curious as to how the über jar could be built. I noticed the “org.springframework.boot” plugin applied in the project downloaded from start.spring.io, and followed course. Since springboot provided a bootRun task, I no longer had use for the “application” plugin, and updated my build.gradle to:
Notice that I have removed the version number from line 21. This is due to the fact that, the gradle-plugin now takes over, and supplies the versions for all spring boot dependencies.
Now, I tried running the application using the plugin’s bootRun task, and by executing the über jar. (But first, “gradle clean build”)
It runs with the bootRun task, however running the über jar complained about SpringBoot’s MainMethodRunner not being able to access a member of the class (in.stackroute.learning.Hello) with modifiers “public static transient”. I declared Hello class as public, and the newly generated über jar, runs like a charm.
I went on and tried creating a Web server with Spring MVC, to confirm that the über jar was working fine. The final source code can be obtained from this github repository.
Update 1: A day after this exercise, I was fascinated by the introduction of modules in Java9. I am in the process of assessing how this might impact this build script, and will write another story if the changes are significant.
Update 2: Configuring modules in gradle is a temporary ugly process. A walkthrough is provided in this official guide, saving me some time and effort.