SpringBoot单元测试(实例)
@toc
测试实例
这里我们分别使用@WebMvcTest和@SpringBootTest两种方式测试一个控制器方法是否满足测试用例。
1、创建基于Spring Data JPA的Web应用ch9_2
2、由于我springBoot是2.4.4,需要额外引入JUnit
在pom.xml文件中添加
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
3、配置数据库连接等基本属性
修改配置文件application.properties如下:
server.servlet.context-path=/ch9_2
###
##数据源信息配置
###
#数据库地址
spring.datasource.url=jdbc:mysql://localhost:3306/springbootjpa?characterEncoding=utf8
#数据库用户名
spring.datasource.username=root
#数据库密码
spring.datasource.password=123456
#数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
####
#JPA持久化配置
####
#指定数据库类型
spring.jpa.database=MYSQL
#指定是否在日志中显示SQL语句
spring.jpa.show-sql=true
#指定自动创建、更新数据库表等配置,update表示如果数据库中存在持久化类对应的表就不创建,不存在就创建对应的表
spring.jpa.hibernate.ddl-auto=update
#让控制器输出的JSON字符串格式更美观
spring.jackson.serialization.indent-output=true
4、创建持久化实体类
创建名为com.ch.ch9_2.entity的包,并添加Student实体类
package com.ch.ch9_2.entity;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity
@Table(name = "student_table")
/**解决No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor异常*/
@JsonIgnoreProperties(value = {"hibernateLazyInitializer"})
public class Student implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;//主键
private String sno;
private String sname;
private String ssex;
public Student() {
super();
}
public Student(int id, String sno, String sname, String ssex) {
super();
this.id = id;
this.sno = sno;
this.sname = sname;
this.ssex = ssex;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSno() {
return sno;
}
public void setSno(String sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSsex() {
return ssex;
}
public void setSsex(String ssex) {
this.ssex = ssex;
}
}
5、创建数据访问层
创建名为com.ch.ch9_2.repository的包,并在改包中创建数据访问接口StudentRepository
package com.ch.ch9_2.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.ch.ch9_2.entity.Student;
public interface StudentRepository extends JpaRepository<Student, Integer>{
}
6、创建控制器层
创建名为com.ch.ch9_2.controller的包,并添加控制器类StudentController
package com.ch.ch9_2.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ch.ch9_2.entity.Student;
import com.ch.ch9_2.repository.StudentRepository;
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
private StudentRepository studentRepository;
/**
* 保存学生信息
*/
@PostMapping("/save")
public String save(@RequestBody Student student) {
studentRepository.save(student);
return "success";
}
/**
* 根据id查询学生信息
*/
@GetMapping("/getOne/{id}")
public Student getOne(@PathVariable("id") int id){
return studentRepository.getOne(id);
}
}
7、创建测试用例
7.1、创建基于@WebMvcTest的测试用例
使用@WebMvcTest注解测试Controller时,带有@Service以及别的注解组件类不会自动被扫描注册为Spring容器管理的Bean。因此,Controller所依赖的对象必须用@MockBean来模拟实现。
在src/test/java目录下的com.ch.ch9_2包中,创建基于@WebMvcTest的测试用例类WebMvcTestStudentController
package com.ch.ch9_2;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
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.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import com.ch.ch9_2.controller.StudentController;
import com.ch.ch9_2.entity.Student;
import com.ch.ch9_2.repository.StudentRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(SpringRunner.class)
/*仅仅扫描这个StudentController类,即注入StudentController到Spring容器*/
@WebMvcTest(StudentController.class)
public class WebMvcTestStudentController {
//MockMvc是Spring提供的专用于测试Controller类
@Autowired
private MockMvc mvc;
//因为在StudentController类依赖StudentRepository,所以需要mock掉
@MockBean
private StudentRepository studentRepository;
@Test
public void saveTest() throws Exception {
Student stu = new Student(1,"5555","陈恒","男");
ObjectMapper mapper = new ObjectMapper();//把对象转换成JSON字符串
mvc.perform(post("/student/save")
.contentType(MediaType.APPLICATION_JSON_UTF8)//发送JSON数据格式
.accept(MediaType.APPLICATION_JSON_UTF8)//接收JSON数据格式
.content(mapper.writeValueAsString(stu))//传递JSON字符串参数
)
.andExpect(status().isOk())//状态响应码为200,如果不是抛出异常,测试不通过。
.andDo(print());//输出结果
}
@Test
public void getStudent() throws Exception {
Student stu = new Student(1,"5555","陈恒","男");
//模拟StudentRepository,getOne(1)将返回stu对象
BDDMockito.given(studentRepository.getOne(1)).willReturn(stu);
mvc.perform(get("/student/getOne/{id}", 1)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
)
.andExpect(status().isOk())//状态响应码为200,如果不是抛出异常,测试不通过。
.andExpect(jsonPath("$.sname").value("陈恒"))
.andDo(print());//输出结果
}
}
7.2、创建基于@SpringBootTest的测试用例
@SpringBootTest注解告诉Spring Boot去寻找一个主配置类(一个带@SpringBootApplication的类),并使用它启动Spring应用程序的上下文,同时注入所有Bean。
在src/test/java目录下的com.ch.ch9_2包中,创建基于@SpringBootTest的测试用例类SpringBootTestStudentController
package com.ch.ch9_2;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import com.ch.ch9_2.entity.Student;
import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Ch92Application.class)//应用的主程序
public class SpringBootTestStudentController {
//注入Spring容器
@Autowired
private WebApplicationContext wac;
//MockMvc模拟实现对Controller的请求
private MockMvc mvc;
//在测试前,初始化MockMvc对象
@Before
public void initMockMvc() {
mvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
@Transactional
public void saveTest() throws Exception {
Student stu = new Student(1, "5555","陈恒","男");
ObjectMapper mapper = new ObjectMapper();//把对象转换成JSON字符串
mvc.perform(post("/student/save")
.contentType(MediaType.APPLICATION_JSON_UTF8)//发送JSON数据格式
.accept(MediaType.APPLICATION_JSON_UTF8)//接收JSON数据格式
.content(mapper.writeValueAsString(stu))//传递JSON字符串参数
)
.andExpect(status().isOk())//状态响应码为200,如果不是抛出异常,测试不通过。
.andDo(print());//输出结果
}
@Test
public void getStudent() throws Exception {
mvc.perform(get("/student/getOne/{id}", 1)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
)
.andExpect(status().isOk())//状态响应码为200,如果不是抛出异常,测试不通过。
.andExpect(jsonPath("$.sname").value("陈恒"))
.andDo(print());//输出结果
}
}
8、运行
8.1 运行WebMvcTestStudentController测试类,执行结果如下:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /student/save
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json;charset=UTF-8", Content-Length:"51"]
Body = {"id":1,"sno":"5555","sname":"陈恒","ssex":"男"}
Session Attrs = {}
Handler:
Type = com.ch.ch9_2.controller.StudentController
Method = com.ch.ch9_2.controller.StudentController#save(Student)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"7"]
Content type = application/json;charset=UTF-8
Body = success
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = GET
Request URI = /student/getOne/1
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json;charset=UTF-8"]
Body = null
Session Attrs = {}
Handler:
Type = com.ch.ch9_2.controller.StudentController
Method = com.ch.ch9_2.controller.StudentController#getOne(int)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json;charset=UTF-8"]
Content type = application/json;charset=UTF-8
Body = {
"id" : 1,
"sno" : "5555",
"sname" : "陈恒",
"ssex" : "男"
}
Forwarded URL = null
Redirected URL = null
Cookies = []
2021-04-09 21:30:14.183 INFO 23076 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 0
状态码为200说明执行成功了
8.2、执行SpringBootTestStudentController
注意,这次是查了数据库的,和上面的不一样,上面的例子是造了个数据,这个是真的查了数据库之后比较的
我们的数据是
“id” : 1,
“sno” : “5555”,
“sname” : “陈恒”,
“ssex” : “男”
但是数据库里面的数据是:
Hibernate: select student0_.id as id1_0_0_, student0_.sname as sname2_0_0_, student0_.sno as sno3_0_0_, student0_.ssex as ssex4_0_0_ from student_table student0_ where student0_.id=?
MockHttpServletRequest:
HTTP Method = POST
Request URI = /student/save
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json;charset=UTF-8", Content-Length:"51"]
Body = {"id":1,"sno":"5555","sname":"陈恒","ssex":"男"}
Session Attrs = {}
Handler:
Type = com.ch.ch9_2.controller.StudentController
Method = com.ch.ch9_2.controller.StudentController#save(Student)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"7"]
Content type = application/json;charset=UTF-8
Body = success
Forwarded URL = null
Redirected URL = null
Cookies = []
2021-04-09 21:32:14.765 INFO 24340 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@327514f testClass = SpringBootTestStudentController, testInstance = com.ch.ch9_2.SpringBootTestStudentController@5a0f87e5, testMethod = saveTest@SpringBootTestStudentController, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@5b12b668 testClass = SpringBootTestStudentController, locations = '{}', classes = '{class com.ch.ch9_2.Ch92Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@a74868d, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@365185bd, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@4cf4d528, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@df27fae, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@587c290d, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@5e853265], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
2021-04-09 21:32:14.770 INFO 24340 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2021-04-09 21:32:14.770 INFO 24340 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2021-04-09 21:32:14.771 INFO 24340 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 0 ms
Hibernate: select student0_.id as id1_0_0_, student0_.sname as sname2_0_0_, student0_.sno as sno3_0_0_, student0_.ssex as ssex4_0_0_ from student_table student0_ where student0_.id=?
java.lang.AssertionError: JSON path "$.sname"
Expected :陈恒
Actual :张三
<Click to see difference>
点击上面的<Click to see difference>,如下图,可以发现测试成功了。查出来值不符合要求。
9、总结
9.1、@SpringBootTest和@WebMvcTest的区别是什么?
我们在使用@WebMvcTest注解测试Controller时,带有@Service以及别的注解组件类不会自动被扫描注册为Spring容器管理的Bean,而@SpringBootTest注解告诉Spring Boot去寻找一个主配置类(一个带@SpringBootApplication的类),并使用它来启动Spring应用程序上下文,注入所有Bean。另外,还需要注意的是,MockMvc用来在Servlet容器内对Controller进行单元测试,并未真正发起了HTTP请求调用Controller。
@WebMvcTest用于从服务器端对Controller层进行统一测试;如果需要从客户端与应用程序交互时,应该使用@SpringBootTest做集成测试。
- 点赞
- 收藏
- 关注作者
评论(0)