Spring and Spock: Happy Together

Written by Jeff Ortman
on February 18, 2021

Editor’s note: You might remember our past post on Spring and Mockito. This is an update to the popular post.

Things change for the better

About four years ago I wrote a blog post describing how to use Mockito to mock dependencies within a Spring test.  You can find that blog post here: https://solutiondesign.com/insights/spring-and-mockito-happy-together/.  In the last four years a lot has changed.  There has been a rapid growth of new technologies, frameworks, and tools.  One of those new frameworks is a testing framework called Spock.  Spock has been around since 2008 but has seen a wide adoption in the last number of years.  Spock tests are written in Groovy and Spock uses Junit as its test runner under the covers.  

Spock includes the ability to mock dependencies natively without including a separate mocking framework such as Mockito.  This post will not cover how to write a Spock test as the online documentation for Spock is excellent.  For a quick overview of Spock that can get you up and running quickly, see the Spock Primer in the References section below.   

Here is an example test that mirrors the same test in the Spring and Mockito blog post but using Spock instead of JUnit and Mockito: 

 


The above test is a unit test where the method under test is the AccountService createNewAccount method.  All dependencies of the createNewAccount method are mocked as you can see at the start of the test.  The given: block sets up data for the test, the when: block executes the method under test and the then: block is where assertions are made to validate what happened.  Notice the absence of the assert keyword used in JUnit.  The assertion is implicit  each line will fail if the expression is false and pass if the expression is true.  It is beyond the scope of this post to go into detail on each assertion in the test.  However, I would like to highlight the accountDAO.save(_) assertion.  Spock will make the argument passed to the save() method available in a closure that follows the method call and it can then be validated in the closure (where the assert keyword does need to be used).  After the argument is validated, the persistedAccount object that was created at the start of the test is then returned from the mocked save() method call. 

As discussed in the Spring and Mockito blog, there are times when you want to write a test that loads the Spring application context to validate that the Spring autowiring is working as expected or, for example, to validate that the data access code works to write to and read from the database.  This is simple with Spock.  All you do is include the Spock Spring module as a dependency in your build script.  Spock’s Spring module enables integration with the Spring TestContext Framework.  Then when you include an annotation on your test that is recognized as a Spring annotation such as @ContextConfiguration, @SpringBootTest, or @WebMvcTest, the Spring application context will be loaded and Spring beans will be available in the test.  Below is an example Spring test that is used to test saving the Account entity to the database and mirrors the same test in the Spring and Mockito blog post: 


You can see in the test above that the AccountDAO and the EntityManager are both Spring managed beans that are injected into the Spock test.  An H2 in-memory database is used in the test to validate that the Account entity object can be saved to the database and read back out. By using a Spring test instead of a Spock test, the actual Hibernate mappings and underlying database queries can be tested. 

But what if you want a Spring test to test a service method but need to mock one of the dependencies of a service?  Maybe this dependency makes a call to a third-party system that you cannot guarantee is always available.  With the wide adoption of cloud-based providers such as AWS, you could have a service that calls out to S3 to retrieve data and you do not want to manage AWS credentials for authentication in your tests.  This is also easy to accomplish with Spock.  Below is a Spock test where the AuditService is mocked but the AccountService is not: 


This test mirrors the Spring and Mockito test in the Spring and Mockito blog post.  It is a Spring test because we want to also validate that the Spring Security configuration is correct.  Spock’s @SpringBean annotation causes the mocked AuditService to replace the AuditService that was originally present in the Spring ApplicationContext. The assertion 1 * auditService.notifyDelete(accountId) validates that the mock audit service’s notifyDelete method was called exactly once with the expected accountId. 

The sample code for these tests can be found here.  If you view the sample code, you will notice the detailed and somewhat complex JPA spring configuration and that this is a non-Spring Boot project.  An effort was made to keep the code as consistent as possible with the code from the previous blog post except for upgrading libraries and using Gradle instead of Maven.  If starting with a new project, I would highly recommend using Spring Boot.  Most of the configuration would not be needed as Spring Boot automatically configures Hibernate as the default JPA provider and starts an embedded H2 database if the H2 library is found on the classpath.  The @SpringBootTest annotation also comes with a lot of functionality and makes Spring tests easier to write. 

I hope this article is helpful in demonstrating how to use Spock and Spring together to create clean, flexible, and powerful tests.  Please see the references below for more information and examples. 

References 

  • http://spockframework.org/spock/docs/1.3/index.html 
  • http://spockframework.org/spock/docs/1.0/spock_primer.html 
  • http://spockframework.org/spock/docs/1.3/module_spring.html 
  • https://www.baeldung.com/the-persistence-layer-with-spring-and-jpa 
  • https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html 

The full source code used for this article can be found on our git repository.