如何写测试 七
在这一章中,我们就以我们之前写过的Todo项目,改造为一个实际接入数据库,并通过Spring boot对外暴露web接口的项目。并对这个项目进行集成测试。
那么首先,我们先实现Repository的部分,之后才是web接口。对于Repository的实现,将采用MySQL作为数据库,并且使用Spring Data JPA作为来程序连接数据库的依赖项。
这里首先,我们需要在数据库之中创建出对应的Table
CREATE TABLE todo_items (
`id` int auto_increment, `content` varchar(255) not null, `done` tinyint not null default 0, primary key (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
这里我们将利用id作为唯一标识。
然后在Todo的实体之中,我们也标记了Id
@Entity
@Table(name = “todo_items”) public class TodoItem { @Id @Column(name = “id”) @GeneratedValue(strategy = GenerationType.IDENTITY) private long index; @Column private String content; @Column private boolean done; … } |
然后我们先编写一个Repository的接口。
因为利用到了Spring Data JPA,那么我们只需要让TodoItemRepository 继承官方的Repository的接口即可。
因为Spring Data JPA会在实际运行的时候生成对应的示例,所以无需编写对应的具体实现。
@ExtendWith(SpringExtension.class)
@DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestPropertySource(“classpath:test.properties”) public class TodoItemRepositoryTest { @Autowired private TodoItemRepository repository; @Test public void should_find_nothing_for_empty_repository() { final Iterable<TodoItem> items = repository.findAll(); assertThat(items).hasSize(0); } … } |
再之后就是测试的相关代码
在上面我们通过Autowired注解将仓库类进行了注入。
并且通过ExtendWith,这利用了Junit的扩展机制,让Spring 参与了测试。其中声明的SpringExtension类进行了依赖注入。
DataJpaTest 这个测试采用了Spring Data JPA,从而在实际运行的时候,根据接口定义,获取到了接口实例。并且其还会负责运行之后的数据回滚清理操作。
这里我们将框架和单元测试集成,对数据层进行了一个小小的集成测试。
再之后是Controller层,这里我们考虑使用Rest风格对外暴露接口。
这里我们考虑给Controller层设计几个接口来对外暴露
添加Todo项
完成Todo项
Todo项列表
添加这里我们直接使用POST接口,并且利用JSON的方式传入保存的内容。
POST /todo-items
{ “content”: “foo” } |
然后就是完成一个Todo项,利用PUT接口,进行修改
PUT /todo-items/{index}
{ done: true } |
最后是获取Todo列表,那么就是一个GET请求,获取所有的值
GET /todo-items?all=true |
那么我们就书写相关的API代码
@RestController
@RequestMapping(“/todo-items”) public class TodoItemResource { private TodoItemService service; @Autowired public TodoItemResource(final TodoItemService service) { this.service = service; } @PostMapping public ResponseEntity addTodoItem(@RequestBody final AddTodoItemRequest request) { if (Strings.isNullOrEmpty(request.getContent())) { return ResponseEntity.badRequest().build(); } final TodoParameter parameter = TodoParameter.of(request.getContent()); final TodoItem todoItem = this.service.addTodoItem(parameter); final URI uri = ServletUriComponentsBuilder .fromCurrentRequest() .path(“/{id}”) .buildAndExpand(todoItem.getIndex()) .toUri(); return ResponseEntity.created(uri).build(); } … } |
通过RestController,来在Spring中声明这是一个入口类
然后利用RequestMapping和PostMapping来声明服务路径。
其中我们还利用RequestBody来声明了一个请求体。
那么我们需要进行相关的测试
@SpringBootTest
@AutoConfigureMockMvc @Transactional public class TodoItemResourceTest { @Autowired private MockMvc mockMvc; @Autowired private TodoItemRepository repository; … . @Test public void should_add_item() throws Exception { String todoItem = “{ ” + “\”content\”: \”foo\”” + “}”; mockMvc.perform(MockMvcRequestBuilders.post(“/todo-items”) .contentType(MediaType.APPLICATION_JSON) .content(todoItem)) .andExpect(status().isCreated()); assertThat(repository.findAll()).anyMatch(item -> item.getContent().equals(“foo”)); } … } |
其中我们仍然是利用SpringBootTest注解,来声明这一次将所有的组件组合的集成测试
之后是AutoConfigureMockMvc,声明内部利用了模拟的网络环境
然后是Transactional,说明测试是事务性的,其中生成的数据在日后要进行回滚。
再之后利用mockMvc来进行相关的请求模拟,声明了请求的路径,参数类型和参数。
并且在查询数据库判断是否有传入的todo项。
那么总结一下,我们将原本的Todo应用从一个命令行应用扩展为一个REST服务,其中增加一个Repository和一个Controller层。
之后将两者进行了集成测试,并且在其中保证了数据回滚。保证了数据的可测试性。