一.序
最近在搞Spring boot的接口测试的问题,为了保证代码的质量和功能实现,想想好像只有Junit可以帮我搞一搞。但是很多问题接踵而至。
我的服务和其它服务有关联(有点像微服务),那在脱离了其它服务依赖的时候,我该如何进行测试?
那么神奇的我找了很多资料,总算总结了以下两个可执行的东西。
(一)Mock可以对数据进行打桩,模拟对象返回
(二)restassured可以简化 HTTP Builder 顶层 ,验证REST服务
本身rest-assured是没办法进行数据打桩的,所以要搭配Mock在一起。
而Mock本身也是不跟rest-assured有什么关联。但是!!!rest-assured将MockMVC集合在一起了
,所以两个的配合得到了一定保证。
二.Mock
mock就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象
可以解决以下两个问题:
1.验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
2.指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
而Mock,我们经常使用的就是Mockito里面的东西。
在Springboot中,我们增加依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
创建TestControl,并构造hello方法
public class TestController {
@RequestMapping("/hello")
@ResponseBody
public String hello() {
return "user";
}
}
在JunitTest类中引入静态资源
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
然后我们来使用mockito进行测试。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestControllerTest {
@MockBean
TestController testController;
@Test
void hello() throws RetException {
//.当调用control中的he方法,则返回我们指定的值。。
Mockito.when(testController.he()).thenReturn("ReTurnValue");
//执行后,可以发现,本身返回"user"的方法,现在返回"ReTurnValue"
System.out.println(testController.he());
}
}
这里只介绍最简单的用法,总结一个Mockito就是模仿你本身的对象,并且返回你自己“设定”的返回值。可以看到,Mockito的职能并不包含去测试Controller层
更多的mockito使用方法
不过为了更好理解下面的合体,这里还要在多介绍一些注解。
可以看到Mock对象是加了@MockBean注解的。
这里可以看大佬写的
@McokBean
:是将模拟对象添加到Spring应用程序上下文中,这个模拟会替换上下文里面任何相同类型 Control层里面有Service.此时,模拟Service.
@Mock
:Mock声明的对象,对函数的调用均执行mock(即虚假函数),不执行真正部分。
@Spy
:声明的对象,对函数的调用均执行真正部分。保留真实对象,除非你设定了确定的返回,否则按照真实的走。
@InjectMock
:InjectMock声明的对象,创建该类的实例,并将使用@Mock注解创建的模拟注入该实例(不懂的看到后面合体就知道了)
可以说其余用@Mock注解创建的mock就会注入到这个实例里面。。
MockBean和Mock的区别:
MockBean会替代所有 上下文中所有相同类型的Mock.Mock只是模拟对象。
Mock和Spy的区别:
Mock模拟的对象里面的方法,如果没有设备返回,那么都是返回null,spy的话如果没有设定其中的方法,那么走的就是真实对象里的方法。
Mock和InJectMock的区别:
Controller层里面要调用Service层,那我Mock的是Service层的接口,那我去调用Controller层的时候,这个Mock的Service就要通过这个方式注入。
三.rest-assured
Rest-Assured是一个REStfulApi测试的利器,可以很方便的去测试自己的API。主要就是调用你自己Controller层来完成自己的测试。当然,它属于一个框架,如果你不喜欢用它,直接用MockMVC也是可以的,那你不继续往下看也是可以的。
我们先来看最基础的调用。
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--做测试用的-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!--做测试用的-->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
在JunitTest类中,添加静态依赖
io.restassured.RestAssured.*
io.restassured.matcher.RestAssuredMatchers.*
org.hamcrest.Matchers.*
这里也加上了json数据的使用方法.
@Test
void userMessage() {
Base_User_R x = new Base_User_R();
x.setA("a");
x.setO("o");
//这里将模拟http请求发送json数据到APi接口
given().contentType("application/json").body(x).log().all().
when().post("http://localhost:8088/Get/User/Message").//输出到接口
then().
assertThat().body("data.age", equalTo(18)).//断言判断返回的json中data:{age:""}是否等于18
assertThat().body("data.name", equalTo("louki")).
assertThat().body("code",equalTo(200));
}
这里只做实例,其它方法就暂时先不放出来。执行后,根据log,可以看到方法执行的是否正常。
四.restassuredMvc和Mock
最重要的来了!!!
这是我们的Controller层
public class TestController{
//路径映射,对应浏览器访问的地址,访问该路径则执行下面函数
@RequestMapping("/hello1")
@ResponseBody
public String hello1() {
return gTestServer.he();
}
这是Service层
public class TestServer {
public String he(){
return "hehehehee";
}
}
如果你直接使用rest-assured,然后使用Mock去模拟了一个方法的返回。我一开始认为,这样是没什么问题的,毕竟。我Mock了Service中的调用。但是显而易见,悲剧是一定会发生的。
@MockBean
TestServer testServer;
void userMessage2() throws RetException {
Base_User_R x = new Base_User_R();
x.setA("a");
x.setO("o");
x.setUseraccount("louki_test");
//这个是隔离服务的.当调用control的这个方法且,x为这样的时候,返回。。
Mockito.when(testServer.he()).thenReturn("ReTurnValue");
System.out.println(testServer.he());
RestAssured.given().
contentType("application/json").body(x).log().all().
when().post("/hello1").
then().log().all().
assertThat().body(containsString("ReTurnValue"));
}
根据log可以看出,在调用Service.he方法时,确实已经根据我们设置的返回,返回的是"ReTurnValue"。但是在Rest-Assured却没有调用Mock中的方法
。
后面我看到,Mockito一般是搭配着MockMVC去做Api的测试,那Rest-Assured里面也是继承了MockMVC的,那我就开始尝试Rest_AssuredMVC去搭配MockMVC的去使用。
依赖不变,Controller,Service层不变。
我们改变使用的是RestAssuredMockMvc中given方法.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class Get_UserControlTest {
@MockBean
TestServer testServer;
//这两个注解很重要,如果不加,也是造成同样的结果.
//Autowired不加,则造成空指针
@InjectMocks
@Autowired
TestController xxx;
@Test
void userMessage() throws RetException {
Base_User_R x = new Base_User_R();
x.setA("a");
x.setO("o");
x.setUseraccount("louki_test");
//这个是隔离服务的.当调用control的这个方法且,x为这样的时候,返回。。
Mockito.when(testServer.he()).thenReturn("ReTurnValue");
System.out.println(testServer.he());
RestAssuredMockMvc.given().standaloneSetup(xxx).
contentType("application/json").body(x).log().all().
when().post("/hello1").
then().log().all().
assertThat().body(containsString("ReTurnValue"));
}
}
运行结果,皆大欢喜。
这里需要注意的是,Control层需要用@InjectMocks进行注解。而我们模拟Mock的Service层需要用@Mock注解。这样就把我们模拟的Mock注入到我们调用的Restassured中
。
这中间真的尝试了很多方法,有好多坑。而且我发现没有放在一起用的,只能靠自己一步一步摸索,在此记录一下,如果对你有用的,可以点一个小小赞~谢谢。