使用spring rest docs 进行单元测试

Spring REST Docs 帮助你管理 RESTful 服务文档,使用 Asciidoctor 来编写文档,使用 Spring MVC Test 自动生成片段。

首先什么样的风格叫做rest风格呢?

编程界的大家应该都有听说过restful风格能够提高项目的性能,而且现在到处都在说我们的项目是rest风格的,那么什么是rest风格呢?简单来举个例子 大家就能懂啦:
比如说现在有这样一个url:
https://www.oschina.net/news/66853/spring-rest-docs-1-0-0
这里的66853可能是一个专用的id或者其他相关的映射,我们从这里是不能知道它到底指的是什么。
而如果是非restful风格的url则会是这样:
https://www.oschina.net/news/uid?userUid=66853/spring-rest-docs-1-0-0
XXX?XXX= XXX这样大家都能猜出来的url,后来加密之后发送到后台,然后就出现了所谓的rest比较流行。

如何使用spring rest doc 生成api文档?

例子:SpringBoot工程,将http接口通过API文档暴露出来,只需要通过JUnit单元测试和spring的MockMVC就可以生成文档。
SpringRestDoc框架通过测试来生成REST接口的说明文档:可以对参数和返回值进行简单的说明,还能产生url和返回用例,通过单元测试,和目前的moxkMVC框架相结合,可以产生Asciidoctor文档片段。
缺点:目前只能生成片段,继承一整个API文档的话还需要手动去写。当然测试的越多,文档的内容就越详细。
解决方案:这里的只能产生片段文章,不能自动整合成一整个文档的缺点,可以使用我上一篇介绍的swagger去解决。swagger具有较强的能力和自信能做好这件事情 ,你就放心交给它好了。

基础配置

软件:idea
jdk:1.8
maven:3.0+

简单粗暴,咱直接从操作开始哈:

1.在pom.xml文件中引入对应的maven依赖

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.restdocs</groupId>
      <artifactId>spring-restdocs-mockmvc</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

2.再测试类中添加设置:

restdoc是通过单元测试生成snippets文件,然后snippets根据插件生成html文档
在测试类中的设置:
(1)

public JUnitRestDocumentation restDocumentation =
new JUnitRestDocumentation("target/generated-snippets");

(2) Then 在@before方法中配置MockMVC或者REST assured:

Setting up your JUnit tests

private MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}

MockMvc实例是使用一个配置MockMvcRestDocumentationConfigurer来配置的。这个类的一个实例可以是从org.springframework.restdocs.mockmvc.MockMvcRestDocumentation中的静态documentationConfiguration()方法获得。

Setting up your tests without JUnit
与上面不同的是不需要@Rule,并且换成了JUnitRestDocumentation

private ManualRestDocumentation restDocumentation =
new ManualRestDocumentation("target/generated-snippets");

同样的,setup()方法中MockMVC的设置将变成这样:

private MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
@BeforeMethod
public void setUp(Method method) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
this.restDocumentation.beforeTest(getClass(), method.getName());
}

但是在每个测试之后ManualRestDocumentation.afterTest()方法都将被调用一次:

@AfterMethod
public void tearDown() {
this.restDocumentation.afterTest();
}

3.Invoking the RESTful service

the testing framework被配置好之后,它就可以尝试去集成RESTful服务了,针对其中的请求和回复进行相应详细的说明。
举个栗子:(kotlin

    @Resource private lateinit var context: WebApplicationContext
    private lateinit var mockMvc: MockMvc

    //设置测试片段生成文件夹的位置
    @Rule @JvmField var restDocumentation = JUnitRestDocumentation("target/asciidoc/snippets")

    @Before
    fun setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .apply<DefaultMockMvcBuilder>(documentationConfiguration(this.restDocumentation))
                .build()
    }

@Test
    fun create() {
    val parentId = "sssssssssss"
    val request = post("/groups/$parent")
    this.mockMvc.perform(request)
                .andException(status.isCreated)
                .andDo(print())
                .andDo(document("{methodName}", preprocessRequest(prettyPrint())))
    }

举个Spring REST Docs的官方文档中的例子:(java)
我们可以在andDo()中添加一些对参数的具体描述:

this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072,0.1275))
            .andExpect(status().isOk())
            .andDo(document("locations",
            pathParameters(parameterWithName("latitude").description("The location's latitude"),parameterWithName("longitude").description("The location's longitude")
)));

关于这些Spring REST Docs的官方文档中对参数的具体描述的一些用法我就不详细赘述了,因为这个很显然,代码上看起来不整齐,此时也已经有更好的东西代替它了,所以我们直接学习更好的解决“为属性和回复消息进行详细说明”的问题的解决方案就好啦~

以上是在SpringRESTDocs的官方文档中学习到的知识,做以简单的总结。

那么有什么好的解决方案呢?
就是我上一篇文章提到的springfox-swagger,它是一个能力的强者,为什么呢?话不多说,举个简单的栗子:
使用springfox-swagger这个框架的时候,我们是把api的详细注释都添加在Controller层的各个类中:
(kotlin)当然啦,想使用下面这段代码的模式,就需要配置集成springfox-swagger在你的项目中。(SpringBoot+SpringDataJPA+SpringFramework+Springfox-swagger)

@RestController
@Api(value = "User", description = "the chapter of user")
@RequestMapping("/users", produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))
class ItemController {

    @ApiOperation(value = "createUser", notes = "创建一个user")
 @ApiImplicitParams(
    ApiImplicitParam(name = "Name", value = "用户名", paramType = "path", required = true, dataType = "String"),
    ApiImplicitParam(name = "age", value = "用户的年龄", paramType = "path", required = true, dataType = "Int")
    )
    @ApiResponses(
            ApiResponse(code = 200, message = "创建成功", response = User::class),
            ApiResponse(code = 404, message = "没有找到", response = Void::class)
    )
    @PostMapping("{name}/age/{age}", consumes = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))
    fun create(@PathVariable("name") userName: String, @PathVariable("age") userAge: Int): ResponseEntity<Any> {
        //此处实现的代码省略
        //......
        return user
    }
}

关于Api的一些简单的注解,都在上一篇中有描述。

总结
(1)springRESTDocs主要是用来在测试的时候生成测试代码片段,并且在测试片段中添加一些简单的说明;
(2)swagger主要是用来提供测试界面,并整合生成API接口文档;
(3)MockMVC是一个属于spring MVC的测试框架,基于Restful风格的SpringMVC的测试,可以测试完成的流程。关于MockMVC的相关信息可以查看网址:http://www.cnblogs.com/lyy-2016/p/6122144.html他写的很棒,也可以看我之前写的博客。

代码举例:

下面 举一个完整的例子,结合springBoot+SpringDataJPA+SpringMVC+MockMVC+SpringRESTDocs+Springfox-swagger,完成生成一个具体的详细的API接口文档。

第一步:在Maven中配置Swagger2+SpringRESTDocs的jar包的依赖

mavne会自动下载你所需要的jar包

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.restdocs</groupId>
            <artifactId>spring-restdocs-mockmvc</artifactId>
            <version>1.1.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-staticdocs</artifactId>
            <version>2.6.1</version>
        </dependency>

第二步:配置Swagger2

    import org.springframework.context.annotation.Bean
    import org.springframework.context.annotation.Configuration
    import org.springframework.http.ResponseEntity
    import springfox.documentation.builders.ApiInfoBuilder
    import springfox.documentation.builders.PathSelectors
    import springfox.documentation.builders.RequestHandlerSelectors
    import springfox.documentation.service.ApiInfo
    import springfox.documentation.service.Contact
    import springfox.documentation.spi.DocumentationType
    import springfox.documentation.spring.web.plugins.Docket
    import springfox.documentation.swagger.web.UiConfiguration
    import springfox.documentation.swagger2.annotations.EnableSwagger2

    @Configuration
    @EnableSwagger2
    class Swagger2Config {
    @Bean
    fun restfulApi(): Docket {
        return Docket(DocumentationType.SWAGGER_2)
                .genericModelSubstitutes(ResponseEntity::class.java)
                .useDefaultResponseMessages(true)
                .forCodeGeneration(true)
                .select()
                .apis(RequestHandlerSelectors
                    .basePackage("{你扫描API注解的包}"))
                .paths(PathSelectors.any())
                .build()
                .pathMapping("/")
                .apiInfo(apiInfo())
    }

    private fun apiInfo(): ApiInfo {
        return ApiInfoBuilder()
                .title("API文档")
                .description(this.description)
                .termsOfServiceUrl("http://terms.XXX.com")
                .contact(Contact("联系方式"))
                .version("1.0.0")
                .build()
    }

这里的@Configuration注解,让spring加载配置,@EnableSwagger2启用Swagger2
再通过createRestApi函数创建Docket的Bean之后,apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现,本例采用指定扫描的包路径来定义,Swagger会扫描该包下所有Controller定义的API,并产生文档内容(除了被@ApiIgnore指定的请求)。

第三步:设置swagger整合片段生成api的一些规范

    import io.github.robwin.markup.builder.MarkupLanguage
    import io.github.robwin.swagger2markup.GroupBy
    import io.github.robwin.swagger2markup.Swagger2MarkupConverter
    import org.junit.Before
    import org.junit.Test
    import org.junit.runner.RunWith
    import org.springframework.boot.test.context.SpringBootTest
    import org.springframework.http.MediaType
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
    import org.springframework.test.web.servlet.MockMvc
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
    import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
    import org.springframework.test.web.servlet.setup.MockMvcBuilders
    import org.springframework.web.context.WebApplicationContext
    import springfox.documentation.staticdocs.Swagger2MarkupResultHandler.outputDirectory
    import springfox.documentation.staticdocs.SwaggerResultHandler
    import javax.annotation.Resource

    @Test
    @Throws(Exception::class)
    fun convertToAsciiDoc() {

        // 得到swagger.json,写入outputDir目录中
        this.mockMvc.perform(get("/v2/api-docs").accept(MediaType.APPLICATION_JSON))
                .andDo(SwaggerResultHandler.outputDirectory(outputDir).build())
                .andExpect(status().isOk)
                .andReturn()

        // 读取上一步生成的swagger.json转成asciiDoc,写入到outputDir
        // 这个outputDir必须和插件里面<generated></generated>标签配置一致
        Swagger2MarkupConverter.from(outputDir + "/swagger.json")
                .withPathsGroupedBy(GroupBy.XXX)// 按XXX排序
                .withMarkupLanguage(MarkupLanguage.ASCIIDOC)// 格式
                .withExamples(snippetDir)
                .build()
                .intoFolder(outputDir) // 输出
    }

在单元测试的时候,SpringRestDocs会生成一些代码片段,这里就是将片段整合先生成一个json文件,再转换格式的过程。

第四步:在测试类中添加SpringRestDocs和Mockmvc的一些代码,进行单元测试

    @Resource private lateinit var context: WebApplicationContext
    private lateinit var mockMvc: MockMvc

    @Rule @JvmField var restDocumentation = 
                JUnitRestDocumentation("{SpringRestDocs生成测试片段的文件的绝对路径}")

    @Before
    fun setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .apply<DefaultMockMvcBuilder>(documentationConfiguration(this.restDocumentation))
                .build()
    }

其中.apply(documentationConfiguration(this.restDocumentation))就是将MockMVC和SpringRestDocs结合在一起。
@Rule 表示当前是在Junit测试环境下的测试

    @Test
    fun create() {
        val json = """ { "userName" : "userName", "age" : "24" } """
        val request = post("/users")
                .contentType("{生产类型}")
                .content(json)
                .accept("{消费类型}")

        this.mockMvc.perform(request)
                .andExpect(status().isCreated)
                .andDo(print())
                .andDo(document("createUser", preprocessResponse(prettyPrint())))
    }

这里的andDo()之类的用法 ,我在之前的文章中都有讲过。这些都是用来模拟一个Mockmvc的请求。
这里的.andDo(document(“createUser”, preprocessResponse(prettyPrint())))是属于SpringRestDocs在生成的测试代码片段中加的一些简单的注释

第五步:在你设置的swagger2扫描的包下,填写api的注解,完善生成api文档中对属性和回复消息的内容。这里的扫描路径一般情况下都是SpringMVC中的Controller层

 @ApiOperation(value = "createUser", notes = "创建一个user")
 @ApiImplicitParams(
    ApiImplicitParam(name = "Name", value = "用户名", paramType = "path", required = true, dataType = "String"),
    ApiImplicitParam(name = "age", value = "用户的年龄", paramType = "path", required = true, dataType = "Int")
    )
    @ApiResponses(
            ApiResponse(code = 200, message = "创建成功", response = User::class),
            ApiResponse(code = 404, message = "没有找到", response = Void::class)
    )
    @PostMapping("{name}/age/{age}", consumes = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))
    fun create(@PathVariable("name") userName: String, @PathVariable("age") userAge: Int): ResponseEntity<Any> {
       //照例 ,中间实现的一些代码省略
        return User
    }

这里所有的api注解,在单元测试完成之后,运行Swagger2的test,都能被整合到api接口文档中。其他的就不再赘述了。

over啦~
待完善revision

你可能感兴趣的:(mockMVC,springrest,swagger2)