当前位置:   article > 正文

15.Spring Boot单元测试(Service、Mock、Feign、Http Rest API)_@springboottest @runwith(springrunner.class) @auto

@springboottest @runwith(springrunner.class) @autoconfiguremockmvc

目录


Spring Boot专栏目录(点击进入…)



Spring Boot单元测试

(1)引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

**test**表示依赖的组件仅仅参与测试相关的工作,包括测试代码的编译和执行,不会被打包包含进去;spring-boot-starter-test是Spring Boot提供项目测试的工具包,内置了多种测试工具,方便我们在项目中做单元测试、集成测试。

引入spring-boot-starter-test后,该启动器提供了常见的单元测试库:

测试库描述
JUnit一个Java语言的单元测试框架
Spring Test & Spring Boot Test为Spring Boot应用提供集成测试和工具支持
AssertJ支持流式断言的Java测试框架
Hamcrest一个匹配器库
Mockito一个java mock框架
JSONassert一个针对JSON的断言库
JsonPathJSON XPath库

(2)使用单元测试

①单元测试代码写在src/test/java目录下
②单元测试类命名为*Test,前缀为要测试的类名

Spring Boot中单元测试类写在src/test/java目录下,可以手动创建具体测试类,如果是IDEA,则可以通过IDEA快捷键自动创建测试类

系统快捷键
WindowCtrl+Shift+T
MAC⇧⌘T

在这里插入图片描述

一个测试方法主要包括三部分:
1)setup
2)执行操作
3)验证结果


常用注解

(1)@RunWith(SpringRunner.class)

JUnit运行使用Spring的测试支持。SpringRunner是SpringJUnit4ClassRunner的新名字,这样做的目的仅仅是为了让名字看起来更简单一点。


(2)@SpringBootTest

该注解为SpringApplication创建上下文并支持Spring Boot特性。

参数描述
Mock加载WebApplicationContext并提供Mock Servlet环境,嵌入的Servlet容器不会被启动
RANDOM_PORT加载一个EmbeddedWebApplicationContext并提供一个真实的servlet环境。嵌入的Servlet容器将被启动并在一个随机端口上监听
DEFINED_PORT加载一个EmbeddedWebApplicationContext并提供一个真实的servlet环境。嵌入的Servlet容器将被启动并在一个默认的端口上监听(application.properties配置端口或者默认端口8080)
NONE使用SpringApplication加载一个ApplicationContext,但是不提供任何的servlet环境

(3)@AutoConfigureMockMvc

用于自动配置MockMvc


(4)@WebMvcTest

该注解被限制为一个单一的controller,需要利用@MockBean去Mock合作者(如service)。

在开发中,有时候接口需要去远程调用其他的接口,而在单元测试中,如果出现别人的接口没有开发完成或者远程服务不可用的情况,那么单元测试就不能进行下去,这时候就需要使用到下面的测试方法了,可以指定远程调用方法返回一个指定符合规则的返回值,不用受限于远程接口的返回值,让单元测试能够进行下去


(5)@MockBean

在ApplicationContext里为一个bean定义一个Mockito mock;使用此注解注入的类,表明类中的所有方法都使用自定义返回的值,这样在测试的时候就不会真的去调用远程接口,而是返回一个预设的值

调用方法定义:TicketBuyOrderInfoVo getBuyOrderInfoVoByOrderId(@RequestParam(“orderId”) String orderId);

@MockBean
protected AirTicketBuyDataServiceFeignClient buyDataServiceFeignClient;

@Test
public void addComsumptions() {
    String json = "";
    TicketBuyOrderInfoVo orderInfoVo = GsonUtil.gson.fromJson(json, TicketBuyOrderInfoVo.class);
    when(buyDataServiceFeignClient.getBuyOrderInfoVoByOrderId(anyString())).thenReturn(orderInfoVo);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

thenReturn(orderInfoVo):设置调用指定方法之后的返回值,这里是返回一个TicketBuyOrderInfoVo对象;而不使用when().thenReturn()写法,直接调用,方法返回值为Null。


(6)@SpyBean

定制化Mock某些方法。使用@SpyBean除了被打过桩的函数,其它的函数都将真实返回。

使用此注解注入的类,表明类中的某一个方法使用自定义返回的值,在测试时,如果使用到了多个方法,那么只是遵循@SpyBean写法的方法会返回自定义的值,在使用时在使用方法上和@MockBean类似,不过写法有所区别

调用方法定义:AutoOrder getAutoOrderByOrderId(String orderId, Date bizTime);

@SpyBean
protected IAutoTicketDao autoTicketDao;

@Test
public void addComsumptionsOut() {
    String json = "";
    AutoOrder autoOrder = GsonUtil.gson.fromJson(json1, AutoOrder.class);
    doReturn(autoOrder).when(autoTicketDao).getAutoOrderByOrderId(anyString(), anyObject());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

doReturn(autoOrder):设置的调用指定方法之后的返回值;遵循doReturn().when()写法的会返回自定义的返参,如果直接调用的话还是会真正调用服务;而在使用时,方法的入参也有所改变。

AutoOrder getAutoOrderByOrderId(String orderId, Date bizTime),如果参数是String类型,测试时入参为anyString(),如果为对象类型,入参为anyObject(),布尔类型为anyBoolean(),Integer类型为anyInt()…,还有很对类型相对应的方法,而如果方法的入参是一个固定的值,那么在测试时也要写为相同的值


Junit基本注解

如果只是简单的做普通Java测试,不涉及Spring Web项目,可以省略@RunWith注解,根据需要选择不同的Runner来运行测试代码


(1)@BeforeClass

在所有测试方法执行前执行一次,一般在其中写上整体初始化的代码


(2)@AfterClass

在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码

// 注意这两个都是静态方法
@BeforeClass
public static void testStart(){}
@AfterClass
public static void testEnd(){}
  • 1
  • 2
  • 3
  • 4
  • 5

(3)@Before

在每个方法测试前执行,一般用来初始化方法(比如:在测试别的方法时,类中与其他测试方法共享的值已经被改变,为了保证测试结果的有效性,会在@Before注解的方法中重置数据)


(4)@After

在每个测试方法执行后,在方法执行完成后要做的事情


(5)@Test(timeout=1000)

测试方法执行超过1000毫秒后算超时,测试将失败


(6)@Test(expected=Exception.class)

测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败


(7)@Ignore(“not ready yet”)

执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类


(8)@Test

编写一般测试用例用


(9)@RunWith

在Junit中有很多个Runner,他们负责调用测试代码,每一个Runner都有各自的特殊功能,你根据需要选择不同的Runner来运行测试代码


1.Service单元测试

下面是最简单的单元测试写法,顶部只要@RunWith(SpringRunner.class)和@SpringBootTest即可,想要执行的时候,鼠标放在对应的方法,右键选择run该方法即可。

@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnServiceTest {
    @Autowired
    private LearnService learnService;

    @Test
    public void getLearn(){
        LearnResource learnResource = learnService.selectByKey(1001L);
        // assertThat断言
        Assert.assertThat(learnResource.getAuthor(),is("測試"));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

单元测试回滚:单元个测试的时候如果不想造成垃圾数据,可以开启事物功能,在方法或者类头部添加@Transactional注解即可。
关闭回滚:在方法上加上@Rollback(false)注解即可。@Rollback表示事务执行完回滚,支持传入一个参数value,默认true即回滚,false不回滚。

如果使用的数据库是Mysql,有时候会发现加了注解@Transactional也不会回滚,那么就要查看数据库的默认引擎是不是InnoDB,如果不是就要改成InnoDB。

2.使用mock方式单元测试

Spring测试框架提供MockMvc对象,可以在不需要客户端-服务端请求的情况下进行MVC测试,完全在服务端这边就可以执行Controller的请求,跟启动了测试服务器一样。(不必启动工程测试这些接口)

MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。

测试开始之前需要建立测试环境,setup方法被@Before修饰。通过MockMvcBuilders工具,使用WebApplicationContext对象作为参数,创建一个MockMvc对象。

@Controller
@RequestMapping("/learn")
public class LearnController extends AbstractController {
    @Autowired
    private LearnService learnService;
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject addLearn(@RequestBody LearnResource learn){
        learnService.save(learn);
        return AjaxObject.ok();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
@RunWith(SpringRunner.class)
// 这里的Application是springboot的启动类名
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
public class StyleControllerTest {

    @Autowired
    private WebApplicationContext context;
    
	@Autowired
    private MockMvc mockMvc;
    
    private MockHttpSession session;
    private ObjectMapper mapper = new ObjectMapper();

    @Before
    public void setupMockMvc() throws Exception {
        // 初始化MockMvc对象
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
        // 拦截器那边会判断用户是否登录,所以这里注入一个用户
        session = new MockHttpSession();
        User user = new User("root","root");
        session.setAttribute("user",user);
    }

    /**
     * 新增教程测试用例
     * @throws Exception
     */
    @Test
    public void addLearn() throws Exception{
        String json="{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://tengj.top/\"}";
        mvc.perform(MockMvcRequestBuilders.post("/learn/add")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes()) //传json参数
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    @Test
    public void testSend() throws Exception {
        Long id =1l;
        //调用接口,传入添加的用户参数
        mockMvc.perform(MockMvcRequestBuilders.get("/style/listStyleById")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(mapper.writeValueAsString(id)))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andDo(MockMvcResultHandlers.print());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

mockMvc.perform:执行一个请求

MockMvcRequestBuilders.get(“/user/1”):构造一个请求,Post请求就用.post方法
contentType(MediaType.APPLICATION_JSON_UTF8):代表发送端发送的数据格式是application/json;charset=UTF-8
accept(MediaType.APPLICATION_JSON_UTF8):代表客户端希望接受的数据类型为application/json;charset=UTF-8
session(session):注入一个session,这样拦截器才可以通过
ResultActions.andExpect:添加执行完成后的断言
ResultActions.andExpect(MockMvcResultMatchers.status().isOk()):方法看请求的状态响应码是否为200如果不是则抛异常,测试不通过
andExpect(MockMvcResultMatchers.jsonPath(“$.author”).value(“md”)):这里jsonPath用来获取author字段比对是否为md,不是就测试不通过
ResultActions.andDo:添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息


3.使用Feign方式单元测试

以下是Feign接口的单元测试示例,①启动项目,可以测试本jar提供的服务。②不启动服务,改为远程服务地址,可以测试远程jar提供的服务

类似实际应用调用相关服务一样

@FeignClient(value = "loan-server", url = "http://localhost:9070/")
public interface UserServiceFeignClient extends UserServiceClient {
}
  • 1
  • 2
  • 3
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = UserControllerTest.class)
@Import({ FeignAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class })
@EnableFeignClients(clients = UserServiceFeignClient.class)
public class UserControllerTest {
    @Autowired
    private UserServiceFeignClient userServiceFeignClient;

    @Test
    public void getUser() {
        User user = userServiceFeignClient.getSDKUserById(1);
        System.out.println(user);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

4.使用Http Rest API 单元测试

使用RestTemplate发起GET或POST请求,其中@SpringBootTest这两行注释掉就不启动SpringBoot容器直接进行远程调用测试

@RunWith(SpringJUnit4ClassRunner.class)
public class LoanControllerTest {
    private  final  static String url =  "http://localhost:9070/";
    private static RestTemplate restTemplate = new RestTemplate();

    @Test
    public void test(){
        ResponseEntity<String> response = restTemplate.exchange(url + "/loan/getLoanById?id=1",
                HttpMethod.GET,
                new HttpEntity(null),
                String.class);
        System.out.println("result: " + response.getBody());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

断言assertThat

assertThat基本语法:assertThat( [value], [matcher statement] );

参数描述
value想要测试的变量值
matcher statement使用Hamcrest匹配符来表达的对前面变量所期望的值的声明,如果value值与matcher statement所表达的期望值相符,则测试成功,否则测试失败

assertThat优点

(1)以前JUnit提供了很多的assertion语句,如:assertEquals、assertNotSame、assertFalse、assertTrue、assertNotNull、assertNull等,现在有了JUnit 4.4,一条assertThat即可以替代所有的assertion语句,这样可以在所有的单元测试中只使用一个断言方法,使得编写测试用例变得简单,代码风格变得统一,测试代码也更容易维护

(2)assertThat使用了Hamcrest的Matcher匹配符,用户可以使用匹配符规定的匹配准则精确的指定一些想设定满足的条件,具有很强的易读性,而且使用起来更加灵活
使用匹配符Matcher和不使用之间的比较

使用匹配符Matcher和不使用之间的比较

// 想判断某个字符串“s”是否含有子字符串“developer”或“Works”中间的一个
// JUnit 4.4以前的版本:assertTrue(s.indexOf("developer")>-1||s.indexOf("Works")>-1 );
// JUnit 4.4:
// 匹配符anyOf表示任何一个条件满足则成立,类似于逻辑或“||”, 匹配符containsString表示是否含有参数
assertThat(s, anyOf(containsString("developer"), containsString("Works"))); 
  • 1
  • 2
  • 3
  • 4
  • 5

(3)assertThat不再像assertEquals那样,使用比较难懂的“谓宾主”语法模式(如:assertEquals(3, x);),相反,assertThat使用了类似于“主谓宾”的易读语法模式(如:assertThat(x,is(3));),使得代码更加直观、易读。

(4)可以将这些 Matcher 匹配符联合起来灵活使用,达到更多目的


字符相关匹配符

assertThat(testedValue, equalTo(expectedValue));
assertThat(testedString, equalToIgnoringCase(expectedString));
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);
assertThat(testedString, containsString(subString) );
assertThat(testedString, endsWith(suffix));
assertThat(testedString, startsWith(prefix));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
方法描述
equalTo(expectedValue)匹配符断言被测的testedValue等于expectedValue。断言判断数值、字符串和对象之间是否相等,相当于Object的equals方法
equalToIgnoringCase(expectedString)匹配符断言被测的字符串testedString,在忽略大小写的情况下等于expectedString
equalToIgnoringWhiteSpace(expectedString)匹配符断言被测的字符串testedString,在忽略头尾的任意个空格的情况下等于expectedString。注意:字符串中的空格不能被忽略
containsString(subString)匹配符断言被测的字符串testedString包含子字符串subString
endsWith(suffix)匹配符断言被测的字符串testedString以子字符串suffix结尾
startsWith(prefix)匹配符断言被测的字符串testedString以子字符串prefix开始

一般匹配符

assertThat(object,nullValue());
assertThat(object,notNullValue());
assertThat(testedString, is(equalTo(expectedValue)));
assertThat(testedValue, is(expectedValue));
assertThat(testedObject, is(Cheddar.class));
assertThat(testedString, not(expectedString));
assertThat(testedNumber, allOf( greaterThan(8), lessThan(16)));
assertThat(testedNumber, anyOf( greaterThan(16), lessThan(8)));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
方法描述
nullValue()匹配符断言被测object的值为null
notNullValue()匹配符断言被测object的值不为null
is(equalTo(expectedValue))匹配符断言被测的object等于后面给出匹配表达式
is(expectedValue)匹配符is(equalTo(x))的简写,断言testedValue等于expectedValue
is(Cheddar.class)匹配符is(instanceOf(SomeClass.class))的简写,断言testedObject为Cheddar的实例
not(expectedString)not匹配符和is匹配符正好相反,断言被测的object不等于后面给出的object
allOf(greaterThan(8), lessThan(16))allOf匹配符断言符合所有条件,相当于“与”(&&)
anyOf(greaterThan(16), lessThan(8))anyOf匹配符断言符合条件之一,相当于“或”(||)

数值相关匹配符

assertThat(testedDouble, closeTo( 20.0, 0.5 ));
assertThat(testedNumber, greaterThan(16.0));
assertThat(testedNumber, lessThan (16.0));
assertThat(testedNumber, greaterThanOrEqualTo (16.0));
assertThat(testedNumber, lessThanOrEqualTo (16.0));
  • 1
  • 2
  • 3
  • 4
  • 5
方法描述
closeTo(20.0, 0.5)closeTo匹配符断言被测的浮点型数testedDouble在20.0¡À0.5范围之内
greaterThan(16.0)greaterThan匹配符断言被测的数值testedNumber大于16.0
lessThan (16.0)lessThan匹配符断言被测的数值testedNumber小于16.0
greaterThanOrEqualTo (16.0)greaterThanOrEqualTo匹配符断言被测的数值testedNumber大于等于16.0
lessThanOrEqualTo (16.0)lessThanOrEqualTo匹配符断言被测的testedNumber小于等于16.0

集合相关匹配符

assertThat(mapObject, hasEntry("key", "value" ) );
assertThat(iterableObject, hasItem (element));
assertThat(mapObject, hasKey ("key"));
assertThat(mapObject, hasValue(value));
  • 1
  • 2
  • 3
  • 4
方法描述
hasEntry(“key”, “value”)hasEntry匹配符断言被测的Map对象mapObject含有一个键值为“key”对应元素值为“value”的Entry项
hasItem(element)hasItem匹配符表明被测的迭代对象iterable Object含有元素element项,则测试通过
hasKey (“key”)hasKey匹配符断言被测的Map对象mapObject含有键值“key”
hasValue(value)hasValue匹配符断言被测的Map对象mapObject含有元素值value
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/653806
推荐阅读
相关标签
  

闽ICP备14008679号