AEM Unit Testing With Groovy & Spock
In my last post, I explored three different ways to use Groovy in our AEM projects. Using Groovy to build OSGi components, servlets and models seemed the most promising. Now I bring you a fourth one that I find as promising. Unit testing with Groovy & Spock.
There are a lot of topics here. TDD vs. BDD. JUnit vs. Spock. Groovy vs. Java. My goal here is not to give you a complete analysis. That's up to you based on your team composition and client. I am going to give you some context. Then I will show you is how to set it up in an AEM project and show you a few examples.
TDD vs. BDD
TDD is for a small technical audience, such as developers. To test small pieces of code. In AEM this could be a Java Sling model or OSGi service.
Since TDD focuses more on what the code is doing, it follows the Arrange-Act-Assert pattern. And since BDD focuses on describing the user action, it follows the Given-When-Then pattern. TDD uses AAA to describe the same thing BDD does with GWT. They are just idioms for the three phases of a test
Given/Arrange: Set stuff up and get all my ducks in a row.
When/Act: Run the thing I want to test.
Then/Assert: Check that the things I expected actually occurred.
If you have been writing unit tests in AEM, you have been doing TDD. It makes sense because we are testing small units of code. The tests get written by developers. BDD makes more sense for integration testing. A product owner provides acceptance criteria. Which then a tester uses to create automated tests. With Selenium and Cucumber for example.
JUnit vs. Spock
In an AEM project, the de facto standard for unit testing is
The JUnit 5 testing framework
Mockito for stubbing, mocking, and interaction testing
wcm.io AEM Mocks for AEM & Sling specific mocking
Spock is more of a one-stop-shop. It enforces the GWT idiom. It has built-in support for stubbing, mocking, and interaction testing. And best of all, it comes with all the power and convenience of Groovy.
I am not going to do a deep dive into the feature comparison. There is no right or wrong framework to use. It all depends on your goals. When I was trying to decide, these are the goals I had
That I could still run JUnit tests in the same project.
That I could limit the scope where Groovy is used to just unit testing.
That I could still use the wcm.io AEM Mocks.
That tests were easier to read than if they had been written with JUnit alone.
In my last post, I deployed the Groovy 3.0.9 into Felix. I was writing servlets and models that needed it at runtime. Here, we are only going to set up Groovy for the testing scope. I am using an AEM project archetype for these examples.
Update the root pom.xml. You will need to locate the junit-bom dependency and update its version to 5.7.0.
Locate and update the maven-surefire-plugin plugin version to 3.0.0-M5.
Add the following plugin repository. This is where you will be able to download the latest groovy-eclipse-batch version.
Lets switch over to the core/pom.xml file. Add the maven-compiler-plugin as follows. I am using Lombok in my examples so I needed some extra configuration. If you are not, you can omit those sections.
Next, you need to update the maven-surefire-plugin. This is so that it picks up Spock specifications. This is only needed if you plan to follow the naming convention of ending the name of the test class with Spec. It's just a convention. If you don't plan on following it, you can omit this.
And finally, add the following test scoped dependencies
You may have noticed JUnit 4 dependencies for AEM Mocks and Spock. We will need them so we can use @Rule with AemContext. I will expand on that later. If you don't plan on using AEM Mocks, you can exclude them.
A Sample Model & Unit Test
Here is a quintessential Sling model. It represents the items created by a component's multi-field dialog. The only thing to note is that I am using Lombok since I am writing it in Java and not Groovy. If you want a little more background you can read my post about equals() & hashCode() for unit testing.
The challenge here is that Lombok is generating all the boilerplate code. And data will be injected at runtime based on the model annotations. So how do we test all our annotations are correct and our fields match the JCR properties? This is where AEM Mocks comes in handy. Lets write our test using JUnit 5
Overall the test is readable. Lets compare its Spock counterpart
A Sample Servlet & Unit Test
The readability of the example above only improved by a small margin. You may be thinking it is not even worth the trouble using Spock. Let's take a look at a Sling servlet.
For this example, I will need a ServletOutputStream to capture the servlet's output. Since it is in the testing package I will write it with Groovy.
Although I could use AEM Mocks, I won't. I will rely on Spock and Groovy. Here is the JUnit 5 unit test.
Again, readable to a developer. Here is the Spock counterpart.
Here the interaction testing is more clear. In JUnit, I had to split up doReturn() and verify(). With Spock, it becomes one line. This made a little more sense to me because when my code has done something, then I expect the code I am testing to have interacted with collaborators in a certain way.
I met all the requirements I set out for myself. Yet, in the end, it is all subjective. Some people may view Spock as a novelty. I view it as an expressive way of unit testing. And a good unit test is a good way to convey your code's intention. And Groovy makes it fun!