接下来试验下Spock如何与mock对象一起工作,之前的文章中我们已经在TestMockBeansConfig类中定义了PublisherRepository的Spring Bean,如下所示,由于@Primary的存在,使得在运行测试用例时Spring Boot优先使用Mockito框架模拟出的实例。
@Configuration @UsedForTesting public class TestMockBeansConfig { @Bean @Primary public PublisherRepository createMockPublisherRepository() { return Mockito.mock(PublisherRepository.class); } }在BookController.java中添加getBooksByPublisher接口,代码如下所示:
@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(); }在SpockBookRepositorySpecification.groovy文件中添加对应的测试用例,
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) }运行测试用例,发现可以测试通过,在控制器将对象转换成JSON字符串装入HTTP响应体时,依赖Jackson库执行转换,可能会有循环依赖的问题——在模型关系中,一本书依赖一个出版社,一个出版社有包含多本书,在执行转换时,如果不进行特殊处理,就会循环解析。我们这里通过@JsonBackReference注解阻止循环依赖。
分析可以看出,通过Spock框架可以写出优雅而强大的测试代码。
首先看SpockBookRepositorySpecification.groovy文件,该类继承自Specification类,告诉JUnit这个类是测试类。查看Specification类的源码,可以发现它被@RunWith(Sputnik.class)注解修饰,这个注解是连接Spock与JUnit的桥梁。除了引导JUnit,Specification类还提供了很多测试方法和mocking支持。
Note:关于Spock的文档见这里:Spock Framework Reference Documentation
根据《单元测试的艺术》一书中提到的,单元测试包括:准备测试数据、执行待测试方法、判断执行结果三个步骤。Spock通过setup、expect、when和then等标签将这些步骤放在一个测试用例中。
setup:这个块用于定义变量、准备测试数据、构建mock对象等;
expect:一般跟在setup块后使用,包含一些assert语句,检查在setup块中准备好的测试环境
when:在这个块中调用要测试的方法;
then : 一般跟在when后使用,尽可以包含断言语句、异常检查语句等等,用于检查要测试的方法执行后结果是否符合预期;
cleanup:用于清除setup块中对环境做的修改,即将当前测试用例中的修改回滚,在这个例子中我们对publisherRepository对象执行重置操作。