SpringBoot单元测试(实例)

举报
别团等shy哥发育 发表于 2023/02/04 17:04:33 2023/02/04
【摘要】 @toc 测试实例这里我们分别使用@WebMvcTest和@SpringBootTest两种方式测试一个控制器方法是否满足测试用例。 1、创建基于Spring Data JPA的Web应用ch9_2 2、由于我springBoot是2.4.4,需要额外引入JUnit在pom.xml文件中添加 <dependency> <groupId>junit</groupId>...

@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做集成测试。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。