Spring REST Docs 帮助你管理 RESTful 服务文档,使用 Asciidoctor 来编写文档,使用 Spring MVC Test 自动生成片段。
编程界的大家应该都有听说过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比较流行。
例子:SpringBoot工程,将http接口通过API文档暴露出来,只需要通过JUnit单元测试和spring的MockMVC就可以生成文档。
SpringRestDoc框架通过测试来生成REST接口的说明文档:可以对参数和返回值进行简单的说明,还能产生url和返回用例,通过单元测试,和目前的moxkMVC框架相结合,可以产生Asciidoctor文档片段。
缺点:目前只能生成片段,继承一整个API文档的话还需要手动去写。当然测试的越多,文档的内容就越详细。
解决方案:这里的只能产生片段文章,不能自动整合成一整个文档的缺点,可以使用我上一篇介绍的swagger去解决。swagger具有较强的能力和自信能做好这件事情 ,你就放心交给它好了。
软件:idea
jdk:1.8
maven:3.0+
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.restdocsgroupId>
<artifactId>spring-restdocs-mockmvcartifactId>
<scope>testscope>
dependency>
dependencies>
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();
}
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(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 {
//此处实现的代码省略
//......
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接口文档。
mavne会自动下载你所需要的jar包
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.6.1version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.6.1version>
dependency>
<dependency>
<groupId>org.springframework.restdocsgroupId>
<artifactId>spring-restdocs-mockmvcartifactId>
<version>1.1.2.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-staticdocsartifactId>
<version>2.6.1version>
dependency>
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指定的请求)。
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必须和插件里面 标签配置一致
Swagger2MarkupConverter.from(outputDir + "/swagger.json")
.withPathsGroupedBy(GroupBy.XXX)// 按XXX排序
.withMarkupLanguage(MarkupLanguage.ASCIIDOC)// 格式
.withExamples(snippetDir)
.build()
.intoFolder(outputDir) // 输出
}
在单元测试的时候,SpringRestDocs会生成一些代码片段,这里就是将片段整合先生成一个json文件,再转换格式的过程。
@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(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在生成的测试代码片段中加的一些简单的注释
@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