上一章介绍了spring boot 的日志配置, 这一章将跟大家聊聊spring boot的单元测试。
spring boot 使用的是Junit作为单元测试的库. 和我们之前使用Junit没有什么本质区别,只是经过spring boot 包装过的junit, 使用起来更简单一些。好了,我们一起来看看在spring boot 的单元测试的例子吧.
1.我们先新建一个module, 在module中新建Application.java, 跟之前的项目一样
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2.接下来再新建一个service的接口和对应的实现
DemoService.java
public interface DemoService {
/**
* 获取用户名
* @return 用户名
*/
String getUserName();
}
DemoServiceImpl.java
@Service
public class DemoServiceImpl implements DemoService {
@Override
public String getUserName() {
return "test";
}
}
3.步入正题, 针对上面这个service写个test, 先建立一个Test的基类
@SpringBootTest(classes = Application.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
public class BaseTest {
}
这个基类使用了三个注解,分别是什么意思呢?
@SpringBootTest 指定了主工程的入口class。
@RunWith指定JUnit的运行器, 这里使用spring boot封装过的JUnitRunner。
@ActiveProfiles 指定了环境.它的功能就像配置文件中的spring.profiles.active。
设置了这些注解, 这个基类就具有单元测试的功能了.当然在实际项目中,这里还可以定义一些日志之类的全局的变量。
4.建立一个单元测试类
我们再建立一个DemoServiceTest.java 让它继承BaseTest. 在这个Test里注入DemoService, 并对getUserName方法进行测试.代码如下:
@Slf4j
public class DemoServiceTest extends BaseTest {
@Autowired
private DemoService demoService;
@Test
public void testGetUserName () {
String userName = demoService.getUserName();
log.info("userName:{}", userName);
// spring和junit的断言还是有些区别的.
Assert.notNull(userName, "无效的用户名");
org.junit.Assert.assertNotNull(userName, "无效的用户名");
}
}
写好后,鼠标在测试方法上右键debug, 看看是不是可以了。在这个单元测试方法里我们使用了Spring 的断言, 它的用法和JUnit还是有区别的, 具体使用哪个,就看个人喜好了。
5.单元测试的前置和后置注解
在实际项目中,我们写单元测试的时候经常会有些数据准备等前置条件, 或者运行完单元测试后清空测试数据等操作. 那么在这里我们可以使用到 @Before @After @BeforeClass @AfterClass 等标签来完成这些工作. 我们先在上面的单元测试例子中添加一些代码:
@Slf4j
public class DemoServiceTest extends BaseTest {
@Autowired
private DemoService demoService;
@Before
public void beforeTest() {
log.info("测试非静态前置方法");
}
@After
public void afterTest() {
log.info("测试非静态后置方法");
}
@BeforeClass
public static void beforeStaticTest() {
log.info("测试静态前置方法");
}
@AfterClass
public static void afterStaticTest() {
log.info("测试静态后置方法");
}
@Test
public void testGetUserName () {
String userName = demoService.getUserName();
log.info("userName:{}", userName);
// spring和junit的断言还是有些区别的.
Assert.notNull(userName, "无效的用户名");
org.junit.Assert.assertNotNull(userName, "无效的用户名");
}
}
继续运行这个单元测试, 通过日志,我们就能清楚的了解到这4个标签的作用了。
@BeforeClass @AfterClass 是静态的前置后置方法, 当启动单元测试时只运行一次, 并且标注这两个注解的方法必须是static的,否则就会抛出下面异常:
java.lang.Exception: Method XXXX() should be static
@Before @After 是每个单元测试方法的前置和后置方法,运行每个单元测试时, 标注了这两个注解的方法都会运行一次。一般情况下, @BeforeClass @AfterClass 可以用作测试的环境变量, 配置信息加载. @Before @After可以用作测试方法的数据加载和清除。 有了这几个注解,在写单元测试时就可以方便很多。
6.使用MockMvc+JUnit来进行mvc的单元测试
首先建一个Controller的demo, 代码如下:
@Slf4j
@RestController
public class DemoController {
@RequestMapping(value = "/", method = RequestMethod.GET)
String home() {
log.info("Hello World!");
return "Hello World!";
}
@RequestMapping(value = "/hello/{myName}", method = RequestMethod.GET)
String index(@PathVariable String myName) {
log.info("Hello " + myName + "!!!");
return "Hello " + myName + "!!!";
}
@RequestMapping("/getUser")
UserModel getUser(){
UserModel userModel = new UserModel();
userModel.setUserName("test");
userModel.setAge(22);
return userModel;
}
}
这个demo中用到一个数据模型UserModel, 那就在写一个model吧,代码如下:
@Data
public class UserModel implements Serializable {
private static final long serialVersionUID = -84121684240840394L;
/** 用户名*/
private String userName;
/** 年龄 */
private Integer age;
}
这里使用了lombok的@Data减少代码书写。(如对lombok不太了解的可以看看我写的lombok介绍和配置 )
好了, 一个基于restful风格的spring mvc就写完了,. 这个demo中我们包含了springmvc的三种常见接口, 无参数的, 有参数的, 返回数据模型的。接下来,我们就用MockMvc针对这三种接口进行单元测试. 建立一个名叫DemoControllerTest的测试类,同样让它基础BaseTest,完成环境的引入。
因为要使用MockMvc, 所以需要在测试类的头上加上@AutoConfigureMockMvc注解,把MockMvc配置的工作交给spring boot吧。接着写三个接口的单元测试类,代码如下:
public class DemoControllerTest extends BaseTest {
@Autowired
private MockMvc mockMvc;
/**
* 无参数, 有返回值
*/
@Test
public void testHome() {
try {
mockMvc.perform(MockMvcRequestBuilders.get("/")) //进行请求
.andExpect(MockMvcResultMatchers.status().isOk()) // 检查响应状态
.andExpect(MockMvcResultMatchers.content().string("Hello World!")); // 检查返回内容
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 有参数,有返回值
*/
@Test
public void testIndex() {
try {
mockMvc.perform(MockMvcRequestBuilders.get("/hello/test")) // restFul的参数,直接写到uri里
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Hello test!!!"));
// 获取到响应, 用日志输出
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/hello/test")).andReturn();
String content = result.getResponse().getContentAsString();
log.info(content);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 无参数, json格式返回值
*/
@Test
public void testGetUser() {
try {
mockMvc.perform(MockMvcRequestBuilders.get("/getUser"))
.andDo(MockMvcResultHandlers.print()) // 打印请求和响应信息
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8)) // 检查响应类型
.andExpect(MockMvcResultMatchers.jsonPath("$.userName").value("test")) // 检查json格式的响应值
.andExpect(MockMvcResultMatchers.jsonPath("$.age").value(22));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
在这个例子中,我们用到了MockMvc的几个方法, 它们的用法如下:
-
mockMvc.perform() : 执行请求, 参数是由MockMvcRequestBuilders.get来构建的
-
mockMvc.andExpect() : 断言方法
-
MockMvcRequestBuilders.get(): 根据URL来构建请求, 返回一个MockHttpServletRequestBuilder
-
MockMvcResultMatchers.status(): 返回响应状态匹配器,用来断言 isOk() 表示是否200状态
-
MockMvcResultMatchers.content() : 返回响应内容匹配器
-
MockMvcResultMatchers.content().string(): 匹配字符串的返回内容
-
andReturn(): 返回响应内容
-
contentType(): 匹配响应的类型
-
jsonPath(): 匹配JSON的内容
-
andDo():执行请求后要做的事
-
print(): 打印请求和响应的信息到控制台
好了, JUnit的常用方法就介绍到这了。
本章结束
下一章将介绍spring boot 数据访问