Remove CGLib proxy “magic” in favour of an explicit document method
Previously, the name of an output file was automatically determined
by the name of the method from which the mockMvc.perform call was made.
While (somewhat) clever, to support this for non @Test methods, this
required the use of a CGLib proxy to push and pop some context that
kept track of the name of the current method. This meant it only worked
for non-private methods. It also made it hard to look at the code and
see what would and would not be documented.
This commit updates the library to provide an explicit document method
instead. This method takes the path of an output directory and a
MockMvc ResultActions instance, typically returned from a call to
mockMvc.perform. For example:
document("index",
this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk()));
This will perform a GET request to "/", assert that the response is
200 OK and write documentation snippets for the request and response
to a directory named index.
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Stack;
|
||||
|
||||
class DocumentationContext {
|
||||
|
||||
private static final InheritableThreadLocal<Stack<DocumentationContext>> CONTEXTS = new InheritableThreadLocal<Stack<DocumentationContext>>() {
|
||||
|
||||
@Override
|
||||
protected Stack<DocumentationContext> initialValue() {
|
||||
return new Stack<DocumentationContext>();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final Class<?> documentationClass;
|
||||
|
||||
private final Method documentationMethod;
|
||||
|
||||
public DocumentationContext(Class<?> documentationClass, Method documentationMethod) {
|
||||
this.documentationClass = documentationClass;
|
||||
this.documentationMethod = documentationMethod;
|
||||
}
|
||||
|
||||
public static DocumentationContext current() {
|
||||
return CONTEXTS.get().peek();
|
||||
}
|
||||
|
||||
public Class<?> getDocumentationClass() {
|
||||
return documentationClass;
|
||||
}
|
||||
|
||||
public Method getDocumentationMethod() {
|
||||
return documentationMethod;
|
||||
}
|
||||
|
||||
static void push(DocumentationContext context) {
|
||||
CONTEXTS.get().push(context);
|
||||
}
|
||||
|
||||
static void pop() {
|
||||
CONTEXTS.get().pop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlRequest;
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlRequestAndResponse;
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlResponse;
|
||||
|
||||
public class RestDocumentation {
|
||||
|
||||
public static ResultActions document(String outputDir, ResultActions resultActions)
|
||||
throws Exception {
|
||||
return resultActions
|
||||
.andDo(documentCurlRequest(outputDir).includeResponseHeaders())
|
||||
.andDo(documentCurlResponse(outputDir).includeResponseHeaders())
|
||||
.andDo(documentCurlRequestAndResponse(outputDir).includeResponseHeaders());
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,13 @@
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlRequest;
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlRequestAndResponse;
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlResponse;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
||||
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
public class RestDocumentationConfiguration implements MockMvcConfigurer {
|
||||
public class RestDocumentationConfiguration extends MockMvcConfigurerAdapter {
|
||||
|
||||
private String scheme = "http";
|
||||
|
||||
@@ -49,13 +45,6 @@ public class RestDocumentationConfiguration implements MockMvcConfigurer {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> builder) {
|
||||
builder.alwaysDo(documentCurlRequest().includeResponseHeaders())
|
||||
.alwaysDo(documentCurlResponse().includeResponseHeaders())
|
||||
.alwaysDo(documentCurlRequestAndResponse().includeResponseHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestPostProcessor beforeMockMvcCreated(
|
||||
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.runners.model.InitializationError;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
public class RestDocumentationJUnit4ClassRunner extends SpringJUnit4ClassRunner {
|
||||
|
||||
public RestDocumentationJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
|
||||
super(clazz);
|
||||
}
|
||||
|
||||
protected Object createTest() throws Exception {
|
||||
Object testInstance = createProxiedTestInstance();
|
||||
getTestContextManager().prepareTestInstance(testInstance);
|
||||
return testInstance;
|
||||
}
|
||||
|
||||
private Object createProxiedTestInstance() {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(getTestClass().getJavaClass());
|
||||
enhancer.setClassLoader(getTestClass().getJavaClass().getClassLoader());
|
||||
enhancer.setCallback(new DocumentationContextManagingMethodInterceptor(
|
||||
getTestClass().getJavaClass()));
|
||||
return enhancer.create();
|
||||
}
|
||||
|
||||
private static class DocumentationContextManagingMethodInterceptor implements
|
||||
MethodInterceptor {
|
||||
|
||||
private final Class<?> testClass;
|
||||
|
||||
private DocumentationContextManagingMethodInterceptor(Class<?> testClass) {
|
||||
this.testClass = testClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object target, Method method, Object[] args,
|
||||
MethodProxy methodProxy) throws Throwable {
|
||||
DocumentationContext.push(new DocumentationContext(this.testClass, method));
|
||||
try {
|
||||
return methodProxy.invokeSuper(target, args);
|
||||
}
|
||||
finally {
|
||||
DocumentationContext.pop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
public abstract class RestDocumentationResultHandlers {
|
||||
|
||||
public static CurlResultHandler documentCurlRequest() {
|
||||
return new CurlResultHandler("Request.asciidoc") {
|
||||
public static CurlResultHandler documentCurlRequest(String outputDir) {
|
||||
return new CurlResultHandler(outputDir, "request") {
|
||||
@Override
|
||||
public void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws Exception {
|
||||
@@ -46,8 +46,8 @@ public abstract class RestDocumentationResultHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
public static CurlResultHandler documentCurlResponse() {
|
||||
return new CurlResultHandler("Response.asciidoc") {
|
||||
public static CurlResultHandler documentCurlResponse(String outputDir) {
|
||||
return new CurlResultHandler(outputDir, "response") {
|
||||
@Override
|
||||
public void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws Exception {
|
||||
@@ -57,8 +57,8 @@ public abstract class RestDocumentationResultHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
public static CurlResultHandler documentCurlRequestAndResponse() {
|
||||
return new CurlResultHandler("RequestResponse.asciidoc") {
|
||||
public static CurlResultHandler documentCurlRequestAndResponse(String outputDir) {
|
||||
return new CurlResultHandler(outputDir, "request-response") {
|
||||
@Override
|
||||
public void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws Exception {
|
||||
@@ -167,10 +167,13 @@ public abstract class RestDocumentationResultHandlers {
|
||||
|
||||
private final CurlConfiguration curlConfiguration = new CurlConfiguration();
|
||||
|
||||
private String suffix;
|
||||
private String outputDir;
|
||||
|
||||
public CurlResultHandler(String suffix) {
|
||||
this.suffix = suffix;
|
||||
private String fileName;
|
||||
|
||||
public CurlResultHandler(String outputDir, String fileName) {
|
||||
this.outputDir = outputDir;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
CurlConfiguration getCurlConfiguration() {
|
||||
@@ -184,7 +187,7 @@ public abstract class RestDocumentationResultHandlers {
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result) throws Exception {
|
||||
PrintStream printStream = createPrintStream(this.suffix);
|
||||
PrintStream printStream = createPrintStream();
|
||||
try {
|
||||
handle(result, new DocumentationWriter(printStream));
|
||||
}
|
||||
@@ -193,16 +196,10 @@ public abstract class RestDocumentationResultHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
private PrintStream createPrintStream(String suffix)
|
||||
private PrintStream createPrintStream()
|
||||
throws FileNotFoundException {
|
||||
DocumentationContext context = DocumentationContext.current();
|
||||
if (context == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
String path = resolveOutputPath(context);
|
||||
|
||||
File outputFile = new File(path);
|
||||
|
||||
File outputFile = new File(this.outputDir, this.fileName + ".asciidoc");
|
||||
if (!outputFile.isAbsolute()) {
|
||||
outputFile = makeAbsolute(outputFile);
|
||||
}
|
||||
@@ -216,21 +213,7 @@ public abstract class RestDocumentationResultHandlers {
|
||||
outputFile.getPath());
|
||||
}
|
||||
|
||||
private String resolveOutputPath(DocumentationContext context) {
|
||||
String shortClassName = getShortClassName(context.getDocumentationClass());
|
||||
return shortClassName + "/" + context.getDocumentationMethod().getName() + this.suffix;
|
||||
}
|
||||
|
||||
private String getShortClassName(Class<?> clazz) {
|
||||
int index = clazz.getName().lastIndexOf('.');
|
||||
if (index >= 0) {
|
||||
return clazz.getName().substring(index + 1);
|
||||
}
|
||||
return clazz.getName();
|
||||
}
|
||||
|
||||
abstract void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws Exception;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user