SpringBoot 1.4 之后测试代码该如何写

前言

测试在 SpringBoot 1.4 版本之后进行了改进,Testing improvements in Spring Boot 1.4。由于 Spring Boot 1.4 之前的测试方式均属于集成测试。一个单纯的单元测试不应该创建和加载 Spring 的上下文。在 1.4 版本之前想要使用单元测试测试一个带有@Autowired注解的外部 services 的 controller 而不用去加载Spring上下文,是不可能的。


简介

Spring Boot 1.4 解决的另外一个问题是,可以测试一段代码。不用启动服务器。并且不用启动整个Spring上下文,Spring Boot 1.4 通过新的Test Slicing的特性 就可以完成,这个特性被设计成可以至启动一小片的Spring上下文。这时的测试单个的代码片段更加容易了。你可以这样去测试你的应用中的特定代码片段

  • MVC 片段: 通过@WebMvcTest注解测试Controller代码
  • JPA 片段: 通过@DataJpaTest注解测试Spring Data JPA repository代码
  • JSON 片段: 通过@JsonTest注解JSON序列化代码

解决的问题;大型的应用要测试的话,如果它启动Spring上下文,会很耗费时间


SpringBoot 1.4-

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {

}

Or

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class)
public class ApplicationTests {

}

SpringBoot 1.4+

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
public class ApplicationTests {

}

注解

  • @RunWith(SpringRunner.class) 告诉Spring运行使用的JUnit测试支持。SpringRunnerSpringJUnit4ClassRunner的新名字,这个名字只是让名字看起来简单些。
  • @SpringBootTest意思是“带有Spring Boot支持的引导程序”(例如,加载应用程序、属性,为我们提供Spring Boot的所有精华部分)。
    • webEnvironment属性允许为测试配置特定的“网络环境”。
      • MOCK:提供一个Mock的Servlet环境,内置的Servlet容器并没有真实的启动,主要搭配使用@AutoConfigureMockMvc
      • RANDOM_PORT: 提供一个真实的Servlet环境,也就是说会启动内置容器,然后使用的是随机端口
      • DEFINED_PORT:这个配置也是提供一个真实的Servlet环境,使用的默认的端口,如果没有配置就是8080
      • NONE:这是个神奇的配置,跟Mock一样也不提供真实的Servlet环境。
    • classes如果想要加载一个特定的配置,可以用@SpringBootTest的classes属性。在这个实例中,省略classes就意味着测试要首次尝试从任意一个inner-classes中加载@configuration,如果这个尝试失败了,它会在你主要的@SpringBootApplicationclass中进行搜索。

实例

单例测试

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = HelloController.class)
@AutoConfigureWebMvc
public class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;
    
    @Test
    public void index() throws Exception {
        this.mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().contentType("application/json;charset=UTF-8"))
//                .andExpect(view().name("index"))
                .andExpect(content().string(Matchers.containsString("Hello World")))
                .andDo(print());
    }
}
  • @SpringBootTest注解不同@WebMvcTest注解会把自动配置给禁用掉。
    • @WebMvcTest只会将 Spring MVC 的基础架构自动配置,并且仅对使用@Controller,@ControllerAdvice,@JsonComponent注解的bean ,以及FilterWebMvcConfigurerHandlerMethodArgumentResolver类型的bean进行扫描。此时@Component、@Service 或 @Repository注解的bean将不会被扫描到

集成测试

@RunWith(SpringRunner.class)
//@WebMvcTest(controllers = HelloController.class)
//@AutoConfigureWebMvc
public class HelloControllerTest extends C1ApplicationTests {

//    @Autowired
    private MockMvc mockMvc;

    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
    }

    @Test
    public void index() throws Exception {
        this.mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().contentType("application/json;charset=UTF-8"))
//                .andExpect(view().name("index"))
                .andExpect(content().string(Matchers.containsString("Hello World")))
                .andDo(print());
    }
}
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = C1Application.class)
public class C1ApplicationTests {
    
}
  • 如果想要加载所有的应用配置并且使用 MockMVC,就应该使用@SpringBootTest注解并且加上 @AutoConfigureMockMvc注解,而不是使用@WebMvcTest注解。
  • MockMvc 通过模拟 Spring MVC 来测试 MVC 网页应用。可以向一个controller发送模拟的HTTP请求,这样不再需要启动应用服务器。可以通过 MockMvcBuilders 来获取 MockMvc 的实例。
    • (推荐)standaloneSetup():注册一个或多个@Controller实例,并且允许通过编程去配置 Spring MVC 的基础架构 从而来构造一个 MockMvc 的实例。 这跟普通的单元测试很相似,同时也使得一次仅关注一个controller的测试成为可能。
    • webAppContextSetup(): 使用完全被初始化(并且刷新过)了的 WebApplicationContext 来构建一个MockMvc实例。这样使Spring可以加载你的控制层以及它们的所有依赖,从而进行一个完整的集成测试。

方法解析:

  • perform:执行一个RequestBuilder请求,会自动执行 SpringMVC 的流程并映射到相应的控制器执行处理;
  • get:声明发送一个get请求的方法。MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根据uri模板和uri变量值得到一个 GET 请求方式的。另外提供了其他的请求的方法,如:post、put、delete等。
  • param:添加request的参数,假如使用需要发送json数据格式的时将不能使用这种方式,可见后面被@ResponseBody注解参数的解决方法
  • andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确(对返回的数据进行的判断);
  • andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台(对返回的数据进行的判断);
  • andReturn:最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理(对返回的数据进行的判断);

控制台

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /hello
       Parameters = {}
          Headers = {}

Handler:
             Type = nuc.jyg.c1.web.HelloController
           Method = public java.lang.String nuc.jyg.c1.web.HelloController.index()

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=[11]}
     Content type = application/json;charset=UTF-8
             Body = Hello World
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

扩展

事物回滚

@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional

上面两句的作用是,让我们对数据库的操作会事务回滚,如对数据库的添加操作,在方法结束之后,会撤销我们对数据库的操作。

为什么要事务回滚?

  • 测试过程对数据库的操作,会产生脏数据,影响数据的正确性
  • 不方便循环测试,即假如这次将一个记录删除了,下次就无法再进行这个Junit测试了,因为该记录已经删除,将会报错。
  • 如果不使用事务回滚,需要在代码中显式的对增删改数据库操作进行恢复,将多很多和测试无关的代码

这里需要注意,如果使用了事物回滚。那么有些时候对数据库内容进行修改操作后你将不能直观的看到变化。如何判断是否成功了,可以在返回的数据中带上data字段,将修改的数据存进去传向前台。

使用andExpect方法对返回的数据进行判断,用“$.属性”获取里面的数据,如要获取返回数据中的"data.name",可以写成"$.data.name"。下面的例子是判断返回的 data.name = “测试”。

MockHttpServletRequestBuilder.andExpect(jsonPath("$.data.name", is("##")))) 

不同环境的测试

@ActiveProfiles(profiles = "test") 在测试类上面指定profiles,可以改变当前spring 的profile,来达到多环境的测试

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容