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
You can spend some time researching Test Driven Development and Behavior Driven Development. To put it simply
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.
BDD is for a wider audience outside of the development team. Developers, QA testers & product owners. It is an extension of TDD. Instead of small units of code, it focuses more on the behavior. In AEM this could be the behavior of a component on a page, developed with JavaScript, CSS, HTL, and Java.
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
JUnit does not impose any idiom or style on you. It gives you the bare essentials. That is why you need the extra tooling. For example, JUnit has an assertions API. I prefer to use AssertJ.
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.
Setup
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
It is also easy to read. The GWT labels have friendly descriptions. It is using maps as constructors. The == operator will use equals(). The only oddity is the JUnit 4 version of the AemContext.
In the JUnit5 test, the AemContextExtension was providing the AemContext. Spock doesn't know about JUnit 5 extensions. But it can work with JUnit 4 rules.
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.
Conclusion
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!
In this post, I only covered the test blocks and interaction testing. Check out the Spock 2.0 documentation page. It is very interesting. I rarely say this about technical documentation.
Comments