This article was originally published on my website: Using the Spock testing framework in the Spring Boot project

The Spock framework is a testing framework based on the Groovy language. Groovy interoperates well with Java, so you can use the framework to write elegant, efficient, and DSL-like test cases in Spring Boot projects. Spock works with the JUnit framework through the @runwith annotation. Spock can also be used with Mockito(the testing of Spring Boot applications — Mockito).

In this section, we will use Spock and Mockito to write some test cases (including Controller tests and Repository tests) to get a feel for using Spock.

In actual combat

  • According to theBuilding an Application with Spring BootThe description of this article,spring-boot-maven-pluginThe plugin also supports using the Groovy language within the Spring Boot framework.
  • Add Spock framework dependencies to poM files
<! -- test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId>  <scope>test</scope></dependency> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-spring</artifactId> <scope>test</scope> </dependency>Copy the code

  • Create a groovy folder in the SRC /test directory and a com/test/bookpub package in the groovy folder.
  • Add it in the Resources directorypackt-books.sqlFile, as follows:
INSERT INTO author (id, first_name, last_name) VALUES (5, 'Shrikrishna', 'Holla');
INSERT INTO book (isbn, title, author, publisher) VALUES ('978-1-78398-478-7', 'Orchestrating Docker', 5, 1);
INSERT INTO author (id, first_name, last_name) VALUES (6, 'du', 'qi');
INSERT INTO book (isbn, title, author, publisher) VALUES ('978-1-78528-415-1', 'Spring Boot Recipes', 6, 1);Copy the code

  • incom/test/bookpubDirectory creationSpockBookRepositorySpecification.groovyThe document reads:
package com.test.bookpubimport com.test.bookpub.domain.Author import com.test.bookpub.domain.Book import com.test.bookpub.domain.Publisher import com.test.bookpub.repository.BookRepository import com.test.bookpub.repository.PublisherRepository import org.mockito.Mockito import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.SpringApplicationContextLoader import org.springframework.context.ConfigurableApplicationContext import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.web.WebAppConfiguration import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.setup.MockMvcBuilders import spock.lang.Sharedimport spock.lang.Specification import javax.sql.DataSourceimport javax.transaction.Transactional import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebAppConfiguration @ContextConfiguration(classes = [BookPubApplication.class, TestMockBeansConfig.class],loader = SpringApplicationContextLoader.class) class SpockBookRepositorySpecification extends  Specification { @Autowired private ConfigurableApplicationContext context; @Shared boolean sharedSetupDone = false; @Autowired private DataSource ds; @Autowired private BookRepository bookRepository; @Autowired private PublisherRepository publisherRepository; @Shared private MockMvc mockMvc; void setup() { if (! sharedSetupDone) { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); sharedSetupDone = true; } ResourceDatabasePopulator populator = new ResourceDatabasePopulator(context.getResource("classpath:/packt-books.sql")); DatabasePopulatorUtils.execute(populator, ds); } @Transactional def "Test RESTful GET"() { when: def result = mockMvc.perform(get("/books/${isbn}")); then: result.andExpect(status().isOk()) result.andExpect(content().string(containsString(title))); where: isbn | title "978-1-78398-478-7"|"Orchestrating Docker" "978-1-78528-415-1"|"Spring Boot Recipes" } @Transactional def "Insert another book"() { setup: def existingBook = bookRepository.findBookByIsbn("978-1-78528-415-1") def newBook = new Book("978-1-12345-678-9", "Some Future Book", existingBook.getAuthor(), existingBook.getPublisher()) expect: bookRepository.count() == 3 when: def savedBook = bookRepository.save(newBook) then: bookRepository.count() == 4 savedBook.id > -1 } }Copy the code

  • Execute the test case and the test passes
  • Next, try out how Spock works with mock objectsTestMockBeansConfigClassPublisherRepositoryBecause of @primary, Spring Boot takes precedence over the Mockito framework instances when running test cases.
@Configuration @UsedForTesting public class TestMockBeansConfig { @Bean @Primary public PublisherRepository createMockPublisherRepository() { return Mockito.mock(PublisherRepository.class); }}Copy the code

  • Add the getBooksByPublisher interface to bookController.java as follows:
@Autowired
public PublisherRepository publisherRepository;

@RequestMapping(value = "/publisher/{id}", method = RequestMethod.GET)
public List<Book> getBooksByPublisher(@PathVariable("id") Long id) {
    Publisher publisher = publisherRepository.findOne(id);
    Assert.notNull(publisher);
    return publisher.getBooks();
}Copy the code

  • inSpockBookRepositorySpecification.groovyAdd the corresponding test case to the file,
def "Test RESTful GET books by publisher"() {
    setup:
    Publisher publisher = new Publisher("Strange Books")
    publisher.setId(999)
    Book book = new Book("978-1-98765-432-1",
            "Mytery Book",
            new Author("Jhon", "Done"),
            publisher)
    publisher.setBooks([book])
    Mockito.when(publisherRepository.count()).
            thenReturn(1L);
    Mockito.when(publisherRepository.findOne(1L)).
            thenReturn(publisher)

    when:
    def result = mockMvc.perform(get("/books/publisher/1"))

    then:
    result.andExpect(status().isOk())
    result.andExpect(content().string(containsString("Strange Books")))

    cleanup:
    Mockito.reset(publisherRepository)
}Copy the code

  • Can run the test cases and found that the tests pass, the controller will object into a JSON string in the HTTP response body, rely on Jackson, perform the conversion, there may be the problem of circular dependencies — in the modeling of relationship, a book relies on a press, a press has contains many book, when performing the transformation, if not for special treatment, It’s going to loop through. We’re going to go through@JsonBackReferenceAnnotations prevent circular dependencies.

Analysis of the

As you can see, you can write elegant and powerful test code using the Spock framework.

First see SpockBookRepositorySpecification. Groovy file, this class inherits from Specification, tell JUnit class – this class is the test class. Looking at the Specification class source code, you can see that it is embellished with the @runwith (Sputnik.class) annotation, which is the bridge between Spock and JUnit. In addition to booting JUnit, the Specification class provides many test methods and mocking support.

Note: Spock Framework Reference Documentation is available here

According to the book The Art of Unit Testing, unit testing consists of three steps: preparing test data, executing the method to be tested, and judging the results of execution. Spock places these steps in a test case with tags like Setup, Expect, when, and then.

  • Setup: This block is used to define variables, prepare test data, build mock objects, and so on;
  • Expect: Generally used after a Setup block, containing assert statements that check the test environment prepared in the setup block
  • When: Calls the method to be tested in this block;
  • Then: usually used after when. It can contain assertion statements, exception checking statements, etc., to check whether the result of the method to be tested is as expected.
  • Cleanup: used to cleanup changes made to the environment in the setup block by rolling back the changes in the current test case. In this case, we reset the publisherRepository object.

Spock also provides setup() and cleanup() methods that perform prep and cleanup actions for all test cases. In this example, we use the setup method :(1) mock out the web runtime environment to accept HTTP requests; (2) Load the packt-books. SQL file and import the predefined test data. The Web environment only needs to Mock once, so use the sharedSetupDone flag to control it.

Transactional operations are implemented through the @Transactional annotation. If a method is decorated by the annotation, the associated setup() and cleanup() methods are defined to perform operations within a transaction: either all succeed or roll back to the initial state. We rely on this method to keep the database clean and to avoid entering the same data every time.

Spring Boot 1.x series

  1. Automatic configuration of Spring Boot, command-line-runner
  2. Learn about automatic configuration of Spring Boot
  3. Use of Spring Boot’s @propertysource annotation in integrating Redis
  4. How do I customize the HTTP message converter in the Spring Boot project
  5. Spring Boot integration Mongodb provides Restful interfaces
  6. Scope for a bean in Spring
  7. The event dispatcher pattern is used in Spring Boot projects
  8. Error handling practices when Spring Boot provides RESTful interfaces
  9. Spring Boot实战之定制自己的starter
  10. How does a Spring Boot project support both HTTP and HTTPS
  11. How does the custom Spring Boot Starter set automatic configuration annotations
  12. Mockito is used in the Spring Boot project

*** This blog focuses on topics such as backend technology, JVM troubleshooting and optimization, Java interview questions, personal growth and self-management. It provides readers with the experience of working and growing as a front-line developer. We look forward to your learning here.