本文首發於我的網站:在Spring Boot項目中使用Spock測試框架html
Spock框架是基於Groovy語言的測試框架,Groovy與Java具有良好的互操做性,所以能夠在Spring Boot項目中使用該框架寫優雅、高效以及DSL化的測試用例。Spock經過@RunWith註解與JUnit框架協同使用,另外,Spock也能夠和Mockito(Spring Boot應用的測試——Mockito)一塊兒使用。java
在這個小節中咱們會利用Spock、Mockito一塊兒編寫一些測試用例(包括對Controller的測試和對Repository的測試),感覺下Spock的使用。git
<!-- 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>
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);
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 } }
@Configuration @UsedForTesting public class TestMockBeansConfig { @Bean @Primary public PublisherRepository createMockPublisherRepository() { return Mockito.mock(PublisherRepository.class); } }
@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(); }
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) }
能夠看出,經過Spock框架能夠寫出優雅而強大的測試代碼。github
首先看SpockBookRepositorySpecification.groovy文件,該類繼承自Specification類,告訴JUnit這個類是測試類。查看Specification類的源碼,能夠發現它被@RunWith(Sputnik.class)註解修飾,這個註解是鏈接Spock與JUnit的橋樑。除了引導JUnit,Specification類還提供了不少測試方法和mocking支持。web
Note:關於Spock的文檔見這裏:Spock Framework Reference Documentation面試
根據《單元測試的藝術》一書中提到的,單元測試包括:準備測試數據、執行待測試方法、判斷執行結果三個步驟。Spock經過setup、expect、when和then等標籤將這些步驟放在一個測試用例中。spring
Spock也提供了setup()和cleanup()方法,執行一些給全部測試用例使用的準備和清除動做,例如在這個例子中咱們使用setup方法:(1)mock出web運行環境,能夠接受http請求;(2)加載packt-books.sql文件,導入預約義的測試數據。web環境只須要Mock一次,所以使用sharedSetupDone這個標誌來控制。sql
經過@Transactional註解能夠實現事務操做,若是某個方法被該註解修飾,則與之相關的setup()方法、cleanup()方法都被定義在一個事務內執行操做:要麼所有成功、要麼回滾到初始狀態。咱們依靠這個方法保證數據庫的整潔,也避免了每次輸入相同的數據。數據庫
本號專一於後端技術、JVM問題排查和優化、Java面試題、我的成長和自我管理等主題,爲讀者提供一線開發者的工做和成長經驗,期待你能在這裏有所收穫。
後端