使用@WebMvcTest--使用MockMvc框架来模拟HTTP请求进行测试--实现对单个控制器的http模拟测试
1.优点
无需启动内置服务器就可以对Controller中某一个HTTP接口进行测试,减少电脑内存占用和运行springboot时间消耗
2.控制器类简单的方法
package com.xurong.chapter4_test.controller;
import com.xurong.chapter4_test.Entity.Book;
import com.xurong.chapter4_test.repository.ReadingListRepository;
import com.xurong.chapter4_test.service.BookService;
import com.xurong.chapter4_test.service.impl.BookServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* @author xu
* @Description
* @date 2023/7/19 - 9:50
* @Modified By:
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/book")
public class BookController {
private final BookService bookService;
@GetMapping("/all")
public List<Book> getAll() {
return bookService.getAll();
}
@GetMapping("/{id}")
public Book getById(@PathVariable("id") Long id) {
return bookService.getById(id);
}
@PostMapping("/insert")
public void insert(@Valid @RequestBody Book book) {
bookService.insert(book);
}
}
3.使用MockMvc框架来模拟HTTP请求进行测试
package com.xurong.chapter4_test.controller;
import com.xurong.chapter4_test.Entity.Book;
import com.xurong.chapter4_test.service.BookService;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
/**
* @author xu
* @Description
* @date 2023/7/19 - 16:38
* @Modified By:
*/
@WebMvcTest(BookController.class)
class BookControllerTest {
@MockBean
private BookService bookService;
@Autowired
private MockMvc mockMvc;
@Test
void checkMockMvc() {
// 检验MockMvc对象是否注入成功
assertNotNull(mockMvc);
}
// 测试:@GetMapping("/all")
@Test
void getAll() throws Exception {
List<Book> books = new ArrayList<>();
// 模拟正确访问应该返回的List数据值
Book book1 = new Book(1L, "张三", "300", "三国演义", "罗贯中", "诸葛亮,周瑜,曹操");
Book book2 = new Book(2L, "xu", "300", "三国演义", "罗贯中", "诸葛亮,周瑜,曹操");
books.add(book1);
books.add(book2);
when(bookService.getAll())
.thenReturn(books);
// 发送请求,对结果与上面模拟的结果值进行对比验证
this.mockMvc.perform(get("/book/all"))
.andDo(print())
// 验证响应状态码为200
.andExpect(status().isOk())
// 检验List结果集合的大小为2
.andExpect(jsonPath("$.size()").value(2))
// 检验list结合中的第2个对象的reader属性值是否为“xu”
.andExpect(jsonPath("$[1].reader").value("xu"));
}
// 测试:public Book getById(@PathVariable("id") Long id)
@Test
void getById() throws Exception {
// 模拟正确访问应该返回的book数据值
Book book2 = new Book(2L, "xu", "300", "三国演义", "罗贯中", "诸葛亮,周瑜,曹操");
when(bookService.getById(2L)).thenReturn(book2);
// 发送http请求,在请求路径上添加id值
this.mockMvc.perform(
MockMvcRequestBuilders.get("/book/{id}",2L))
.andDo(print())
.andExpect(jsonPath("$.reader").value("xu"));
}
// 测试:public void insert(@Valid @RequestBody Book book)
// 对于@Valid也可以起到验证作用
@Test
void insert() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/book/insert")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"reader\":\"小武a\",\"isbn\":\"400\",\"title\":\"水浒传\",\"author\":\"施耐庵\",\"description\":\"武松:打虎英雄\"}"))
//使用andDo(print())方法打印出请求和响应的详细信息
.andDo(print());
// 使用verify方法验证BookService的insert方法是否成功被调用,并且传入的参数是一个Book对象。
// 这可以通过any(Book.class)来实现,表示任意类型的Book对象都可以作为参数传入
verify(bookService).insert(any(Book.class));
}
}
4.需要注意的地方
注意1:如果BookControllerTest注入了BookService,那么在编写测试方法时需要使用@MockBean注入service的对象,注意不是使用原生的@Resource,@Autowired或者构造器进行注入
@MockBean
private BookService bookService;
注意2:需要将模拟容器注入进来,当然注入的方法有多种,网上自行搜索
@Autowired
private MockMvc mockMvc;
注意3:这一段时发送http请求,MockMvcRequestBuilders有多个方法,也可以添加请求头和请求体等信息
this.mockMvc.perform(
MockMvcRequestBuilders.get("/book/{id}",2L))
注意4:
后面的几个方法是对http请求结果进行验证,andDo方法将测试结果打印,andExpect对http请求结果的期望值;
注意一定要调用(when(bookService.getById(2L)).thenReturn(book2);),否则("$.reader")不会起作用
@Test
void getById() throws Exception {
// 创建了一个Book对象book2,并使用when方法模拟了调用bookService.getById(2L)时应该返回book2对象的情况
Book book2 = new Book(2L, "xu", "300", "三国演义", "罗贯中", "诸葛亮,周瑜,曹操");
when(bookService.getById(2L)).thenReturn(book2);
// 发送http请求,在请求路径上添加id值
this.mockMvc.perform(
MockMvcRequestBuilders.get("/book/{id}",2L))
.andDo(print())
.andExpect(jsonPath("$.reader").value("xu"));
}