Make use of Gradle's support for test fixtures

Closes gh-756
This commit is contained in:
Andy Wilkinson
2021-09-28 10:00:00 +01:00
parent f8fd65b23b
commit ea3ac32701
25 changed files with 82 additions and 73 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -28,13 +28,13 @@ import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.GeneratedSnippets;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.test.SnippetConditions;
import org.springframework.restdocs.test.SnippetConditions.CodeBlockCondition;
import org.springframework.restdocs.test.SnippetConditions.HttpRequestCondition;
import org.springframework.restdocs.test.SnippetConditions.HttpResponseCondition;
import org.springframework.restdocs.test.SnippetConditions.TableCondition;
import org.springframework.restdocs.testfixtures.GeneratedSnippets;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import org.springframework.restdocs.testfixtures.SnippetConditions;
import org.springframework.restdocs.testfixtures.SnippetConditions.CodeBlockCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpRequestCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.TableCondition;
import org.springframework.web.bind.annotation.RequestMethod;
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -25,7 +25,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -25,7 +25,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -26,7 +26,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -23,7 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.test.OutputCapture;
import org.springframework.restdocs.testfixtures.OutputCapture;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.isEmptyString;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -27,9 +27,9 @@ import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.GeneratedSnippets;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.test.SnippetConditions;
import org.springframework.restdocs.testfixtures.GeneratedSnippets;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import org.springframework.restdocs.testfixtures.SnippetConditions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -28,7 +28,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -26,7 +26,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -28,7 +28,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -27,7 +27,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -26,7 +26,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -26,7 +26,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2021 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.
@@ -26,8 +26,8 @@ import org.junit.Test;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.test.GeneratedSnippets;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.testfixtures.GeneratedSnippets;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,132 +0,0 @@
/*
* Copyright 2014-2019 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
*
* https://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.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import org.junit.runners.model.Statement;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.fail;
/**
* The {@code GeneratedSnippets} rule is used to capture the snippets generated by a test
* and assert their existence and content.
*
* @author Andy Wilkinson
* @author Andreas Evers
*/
public class GeneratedSnippets extends OperationTestRule {
private final TemplateFormat templateFormat;
private String operationName;
private File outputDirectory;
public GeneratedSnippets(TemplateFormat templateFormat) {
this.templateFormat = templateFormat;
}
@Override
public Statement apply(Statement base, File outputDirectory, String operationName) {
this.outputDirectory = outputDirectory;
this.operationName = operationName;
return base;
}
public String curlRequest() {
return snippet("curl-request");
}
public String httpieRequest() {
return snippet("httpie-request");
}
public String requestHeaders() {
return snippet("request-headers");
}
public String responseHeaders() {
return snippet("response-headers");
}
public String httpRequest() {
return snippet("http-request");
}
public String httpResponse() {
return snippet("http-response");
}
public String links() {
return snippet("links");
}
public String requestFields() {
return snippet("request-fields");
}
public String requestParts() {
return snippet("request-parts");
}
public String requestPartFields(String partName) {
return snippet("request-part-" + partName + "-fields");
}
public String responseFields() {
return snippet("response-fields");
}
public String pathParameters() {
return snippet("path-parameters");
}
public String requestParameters() {
return snippet("request-parameters");
}
public String snippet(String name) {
File snippetFile = getSnippetFile(name);
try {
return FileCopyUtils
.copyToString(new InputStreamReader(new FileInputStream(snippetFile), StandardCharsets.UTF_8));
}
catch (Exception ex) {
fail("Failed to read '" + snippetFile + "'", ex);
return null;
}
}
private File getSnippetFile(String name) {
if (this.outputDirectory == null) {
fail("Output directory was null");
}
if (this.operationName == null) {
fail("Operation name was null");
}
File snippetDir = new File(this.outputDirectory, this.operationName);
return new File(snippetDir, name + "." + this.templateFormat.getFileExtension());
}
}

View File

@@ -1,300 +0,0 @@
/*
* Copyright 2014-2019 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
*
* https://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.test;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.runners.model.Statement;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.ManualRestDocumentation;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.mustache.Mustache;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestFactory;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.OperationRequestPartFactory;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.RequestCookie;
import org.springframework.restdocs.operation.StandardOperation;
import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolverFactory;
import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.mustache.AsciidoctorTableCellContentLambda;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
/**
* Basic builder API for creating an {@link Operation}.
*
* @author Andy Wilkinson
*/
public class OperationBuilder extends OperationTestRule {
private final Map<String, Object> attributes = new HashMap<>();
private OperationResponseBuilder responseBuilder;
private String name;
private File outputDirectory;
private final TemplateFormat templateFormat;
private OperationRequestBuilder requestBuilder;
public OperationBuilder(TemplateFormat templateFormat) {
this.templateFormat = templateFormat;
}
public OperationRequestBuilder request(String uri) {
this.requestBuilder = new OperationRequestBuilder(uri);
return this.requestBuilder;
}
public OperationResponseBuilder response() {
this.responseBuilder = new OperationResponseBuilder();
return this.responseBuilder;
}
public OperationBuilder attribute(String name, Object value) {
this.attributes.put(name, value);
return this;
}
private void prepare(String operationName, File outputDirectory) {
this.name = operationName;
this.outputDirectory = outputDirectory;
this.requestBuilder = null;
this.requestBuilder = null;
this.attributes.clear();
}
public Operation build() {
if (this.attributes.get(TemplateEngine.class.getName()) == null) {
Map<String, Object> templateContext = new HashMap<>();
templateContext.put("tableCellContent", new AsciidoctorTableCellContentLambda());
this.attributes.put(TemplateEngine.class.getName(),
new MustacheTemplateEngine(new StandardTemplateResourceResolver(this.templateFormat),
Mustache.compiler().escapeHTML(false), templateContext));
}
RestDocumentationContext context = createContext();
this.attributes.put(RestDocumentationContext.class.getName(), context);
this.attributes.put(WriterResolver.class.getName(), new StandardWriterResolver(
new RestDocumentationContextPlaceholderResolverFactory(), "UTF-8", this.templateFormat));
return new StandardOperation(this.name,
((this.requestBuilder == null) ? new OperationRequestBuilder("http://localhost/").buildRequest()
: this.requestBuilder.buildRequest()),
(this.responseBuilder == null) ? new OperationResponseBuilder().buildResponse()
: this.responseBuilder.buildResponse(),
this.attributes);
}
private RestDocumentationContext createContext() {
ManualRestDocumentation manualRestDocumentation = new ManualRestDocumentation(
this.outputDirectory.getAbsolutePath());
manualRestDocumentation.beforeTest(null, null);
RestDocumentationContext context = manualRestDocumentation.beforeOperation();
return context;
}
@Override
public Statement apply(Statement base, File outputDirectory, String operationName) {
prepare(operationName, outputDirectory);
return base;
}
/**
* Basic builder API for creating an {@link OperationRequest}.
*/
public final class OperationRequestBuilder {
private URI requestUri = URI.create("http://localhost/");
private HttpMethod method = HttpMethod.GET;
private byte[] content = new byte[0];
private HttpHeaders headers = new HttpHeaders();
private Parameters parameters = new Parameters();
private List<OperationRequestPartBuilder> partBuilders = new ArrayList<>();
private Collection<RequestCookie> cookies = new ArrayList<>();
private OperationRequestBuilder(String uri) {
this.requestUri = URI.create(uri);
}
private OperationRequest buildRequest() {
List<OperationRequestPart> parts = new ArrayList<>();
for (OperationRequestPartBuilder builder : this.partBuilders) {
parts.add(builder.buildPart());
}
return new OperationRequestFactory().create(this.requestUri, this.method, this.content, this.headers,
this.parameters, parts, this.cookies);
}
public Operation build() {
return OperationBuilder.this.build();
}
public OperationRequestBuilder method(String method) {
this.method = HttpMethod.valueOf(method);
return this;
}
public OperationRequestBuilder content(String content) {
this.content = content.getBytes();
return this;
}
public OperationRequestBuilder content(byte[] content) {
this.content = content;
return this;
}
public OperationRequestBuilder param(String name, String... values) {
if (values.length > 0) {
for (String value : values) {
this.parameters.add(name, value);
}
}
else {
this.parameters.put(name, Collections.<String>emptyList());
}
return this;
}
public OperationRequestBuilder header(String name, String value) {
this.headers.add(name, value);
return this;
}
public OperationRequestPartBuilder part(String name, byte[] content) {
OperationRequestPartBuilder partBuilder = new OperationRequestPartBuilder(name, content);
this.partBuilders.add(partBuilder);
return partBuilder;
}
public OperationRequestBuilder cookie(String name, String value) {
this.cookies.add(new RequestCookie(name, value));
return this;
}
/**
* Basic builder API for creating an {@link OperationRequestPart}.
*/
public final class OperationRequestPartBuilder {
private final String name;
private final byte[] content;
private String submittedFileName;
private HttpHeaders headers = new HttpHeaders();
private OperationRequestPartBuilder(String name, byte[] content) {
this.name = name;
this.content = content;
}
public OperationRequestPartBuilder submittedFileName(String submittedFileName) {
this.submittedFileName = submittedFileName;
return this;
}
public OperationRequestBuilder and() {
return OperationRequestBuilder.this;
}
public Operation build() {
return OperationBuilder.this.build();
}
private OperationRequestPart buildPart() {
return new OperationRequestPartFactory().create(this.name, this.submittedFileName, this.content,
this.headers);
}
public OperationRequestPartBuilder header(String name, String value) {
this.headers.add(name, value);
return this;
}
}
}
/**
* Basic builder API for creating an {@link OperationResponse}.
*/
public final class OperationResponseBuilder {
private int status = HttpStatus.OK.value();
private HttpHeaders headers = new HttpHeaders();
private byte[] content = new byte[0];
private OperationResponse buildResponse() {
return new OperationResponseFactory().create(this.status, this.headers, this.content);
}
public OperationResponseBuilder status(int status) {
this.status = status;
return this;
}
public OperationResponseBuilder header(String name, String value) {
this.headers.add(name, value);
return this;
}
public OperationResponseBuilder content(byte[] content) {
this.content = content;
return this;
}
public OperationResponseBuilder content(String content) {
this.content = content.getBytes();
return this;
}
public Operation build() {
return OperationBuilder.this.build();
}
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2014-2019 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
*
* https://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.test;
import java.io.File;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* Abstract base class for Operation-related {@link TestRule TestRules}.
*
* @author Andy Wilkinson
*/
abstract class OperationTestRule implements TestRule {
@Override
public final Statement apply(Statement base, Description description) {
return apply(base, determineOutputDirectory(description), determineOperationName(description));
}
private File determineOutputDirectory(Description description) {
return new File("build/" + description.getTestClass().getSimpleName());
}
private String determineOperationName(Description description) {
String operationName = description.getMethodName();
int index = operationName.indexOf('[');
if (index > 0) {
operationName = operationName.substring(0, index);
}
return operationName;
}
protected abstract Statement apply(Statement base, File outputDirectory, String operationName);
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.api.HamcrestCondition;
import org.hamcrest.Matcher;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.allOf;
/**
* JUnit {@code @Rule} to capture output from System.out and System.err.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class OutputCapture implements TestRule {
private CaptureOutputStream captureOut;
private CaptureOutputStream captureErr;
private ByteArrayOutputStream capturedOutput;
private List<Matcher<? super String>> matchers = new ArrayList<>();
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
captureOutput();
try {
base.evaluate();
}
finally {
try {
if (!OutputCapture.this.matchers.isEmpty()) {
assertThat(getOutputAsString())
.is(new HamcrestCondition<>(allOf(OutputCapture.this.matchers)));
}
}
finally {
releaseOutput();
}
}
}
};
}
private void captureOutput() {
this.capturedOutput = new ByteArrayOutputStream();
this.captureOut = new CaptureOutputStream(System.out, this.capturedOutput);
this.captureErr = new CaptureOutputStream(System.err, this.capturedOutput);
System.setOut(new PrintStream(this.captureOut));
System.setErr(new PrintStream(this.captureErr));
}
private String getOutputAsString() {
flush();
return this.capturedOutput.toString();
}
private void releaseOutput() {
System.setOut(this.captureOut.getOriginal());
System.setErr(this.captureErr.getOriginal());
this.capturedOutput = null;
}
private void flush() {
try {
this.captureOut.flush();
this.captureErr.flush();
}
catch (IOException ex) {
// ignore
}
}
/**
* Verify that the output is matched by the supplied {@code matcher}. Verification is
* performed after the test method has executed.
* @param matcher the matcher
*/
public final void expect(Matcher<? super String> matcher) {
this.matchers.add(matcher);
}
private static final class CaptureOutputStream extends OutputStream {
private final PrintStream original;
private final OutputStream copy;
private CaptureOutputStream(PrintStream original, OutputStream copy) {
this.original = original;
this.copy = copy;
}
@Override
public void write(int b) throws IOException {
this.copy.write(b);
this.original.write(b);
this.original.flush();
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
this.copy.write(b, off, len);
this.original.write(b, off, len);
}
private PrintStream getOriginal() {
return this.original;
}
@Override
public void flush() throws IOException {
this.copy.flush();
this.original.flush();
}
}
}

View File

@@ -1,361 +0,0 @@
/*
* Copyright 2014-2019 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
*
* https://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.test;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.assertj.core.api.Condition;
import org.assertj.core.description.Description;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* {@link Condition Conditions} for verify the contents of generated documentation
* snippets.
*
* @author Andy Wilkinson
*/
public final class SnippetConditions {
private SnippetConditions() {
}
public static TableCondition<?> tableWithHeader(TemplateFormat format, String... headers) {
if ("adoc".equals(format.getFileExtension())) {
return new AsciidoctorTableCondition(null, headers);
}
return new MarkdownTableCondition(null, headers);
}
public static TableCondition<?> tableWithTitleAndHeader(TemplateFormat format, String title, String... headers) {
if ("adoc".equals(format.getFileExtension())) {
return new AsciidoctorTableCondition(title, headers);
}
return new MarkdownTableCondition(title, headers);
}
public static HttpRequestCondition httpRequest(TemplateFormat format, RequestMethod requestMethod, String uri) {
if ("adoc".equals(format.getFileExtension())) {
return new HttpRequestCondition(requestMethod, uri, new AsciidoctorCodeBlockCondition<>("http", "nowrap"),
3);
}
return new HttpRequestCondition(requestMethod, uri, new MarkdownCodeBlockCondition<>("http"), 2);
}
public static HttpResponseCondition httpResponse(TemplateFormat format, HttpStatus status) {
if ("adoc".equals(format.getFileExtension())) {
return new HttpResponseCondition(status, new AsciidoctorCodeBlockCondition<>("http", "nowrap"), 3);
}
return new HttpResponseCondition(status, new MarkdownCodeBlockCondition<>("http"), 2);
}
public static HttpResponseCondition httpResponse(TemplateFormat format, Integer responseStatusCode,
String responseStatusReason) {
if ("adoc".equals(format.getFileExtension())) {
return new HttpResponseCondition(responseStatusCode, responseStatusReason,
new AsciidoctorCodeBlockCondition<>("http", "nowrap"), 3);
}
return new HttpResponseCondition(responseStatusCode, responseStatusReason,
new MarkdownCodeBlockCondition<>("http"), 2);
}
@SuppressWarnings({ "rawtypes" })
public static CodeBlockCondition<?> codeBlock(TemplateFormat format, String language) {
if ("adoc".equals(format.getFileExtension())) {
return new AsciidoctorCodeBlockCondition(language, null);
}
return new MarkdownCodeBlockCondition(language);
}
@SuppressWarnings({ "rawtypes" })
public static CodeBlockCondition<?> codeBlock(TemplateFormat format, String language, String options) {
if ("adoc".equals(format.getFileExtension())) {
return new AsciidoctorCodeBlockCondition(language, options);
}
return new MarkdownCodeBlockCondition(language);
}
private abstract static class AbstractSnippetContentCondition extends Condition<String> {
private List<String> lines = new ArrayList<>();
protected AbstractSnippetContentCondition() {
as(new Description() {
@Override
public String value() {
return getLinesAsString();
}
});
}
protected void addLine(String line) {
this.lines.add(line);
}
protected void addLine(int index, String line) {
this.lines.add(determineIndex(index), line);
}
private int determineIndex(int index) {
if (index >= 0) {
return index;
}
return index + this.lines.size();
}
@Override
public boolean matches(String content) {
return getLinesAsString().equals(content);
}
private String getLinesAsString() {
StringWriter writer = new StringWriter();
Iterator<String> iterator = this.lines.iterator();
while (iterator.hasNext()) {
writer.append(String.format("%s", iterator.next()));
if (iterator.hasNext()) {
writer.append(String.format("%n"));
}
}
return writer.toString();
}
}
/**
* Base class for code block Conditions.
*
* @param <T> The type of the Condition
*/
public static class CodeBlockCondition<T extends CodeBlockCondition<T>> extends AbstractSnippetContentCondition {
@SuppressWarnings("unchecked")
public T withContent(String content) {
this.addLine(-1, content);
return (T) this;
}
}
/**
* A {@link Condition} for an Asciidoctor code block.
*
* @param <T> The type of the Condition
*/
public static class AsciidoctorCodeBlockCondition<T extends AsciidoctorCodeBlockCondition<T>>
extends CodeBlockCondition<T> {
protected AsciidoctorCodeBlockCondition(String language, String options) {
this.addLine("[source" + ((language != null) ? "," + language : "")
+ ((options != null) ? ",options=\"" + options + "\"" : "") + "]");
this.addLine("----");
this.addLine("----");
}
}
/**
* A {@link Condition} for a Markdown code block.
*
* @param <T> The type of the Condition
*/
public static class MarkdownCodeBlockCondition<T extends MarkdownCodeBlockCondition<T>>
extends CodeBlockCondition<T> {
protected MarkdownCodeBlockCondition(String language) {
this.addLine("```" + ((language != null) ? language : ""));
this.addLine("```");
}
}
/**
* A {@link Condition} for an HTTP request or response.
*
* @param <T> The type of the Condition
*/
public abstract static class HttpCondition<T extends HttpCondition<T>> extends Condition<String> {
private final CodeBlockCondition<?> delegate;
private int headerOffset;
protected HttpCondition(CodeBlockCondition<?> delegate, int headerOffset) {
this.delegate = delegate;
this.headerOffset = headerOffset;
}
@SuppressWarnings("unchecked")
public T header(String name, String value) {
this.delegate.addLine(this.headerOffset++, name + ": " + value);
return (T) this;
}
@SuppressWarnings("unchecked")
public T header(String name, long value) {
this.delegate.addLine(this.headerOffset++, name + ": " + value);
return (T) this;
}
@SuppressWarnings("unchecked")
public T content(String content) {
this.delegate.addLine(-1, content);
return (T) this;
}
@Override
public boolean matches(String item) {
return this.delegate.matches(item);
}
}
/**
* A {@link Condition} for an HTTP response.
*/
public static final class HttpResponseCondition extends HttpCondition<HttpResponseCondition> {
private HttpResponseCondition(HttpStatus status, CodeBlockCondition<?> delegate, int headerOffset) {
super(delegate, headerOffset);
this.content("HTTP/1.1 " + status.value() + " " + status.getReasonPhrase());
this.content("");
}
private HttpResponseCondition(int responseStatusCode, String responseStatusReason,
CodeBlockCondition<?> delegate, int headerOffset) {
super(delegate, headerOffset);
this.content("HTTP/1.1 " + responseStatusCode + " " + responseStatusReason);
this.content("");
}
}
/**
* A {@link Condition} for an HTTP request.
*/
public static final class HttpRequestCondition extends HttpCondition<HttpRequestCondition> {
private HttpRequestCondition(RequestMethod requestMethod, String uri, CodeBlockCondition<?> delegate,
int headerOffset) {
super(delegate, headerOffset);
this.content(requestMethod.name() + " " + uri + " HTTP/1.1");
this.content("");
}
}
/**
* Base class for table Conditions.
*
* @param <T> The concrete type of the Condition
*/
public abstract static class TableCondition<T extends TableCondition<T>> extends AbstractSnippetContentCondition {
public abstract T row(String... entries);
public abstract T configuration(String configuration);
}
/**
* A {@link Condition} for an Asciidoctor table.
*/
public static final class AsciidoctorTableCondition extends TableCondition<AsciidoctorTableCondition> {
private AsciidoctorTableCondition(String title, String... columns) {
if (StringUtils.hasText(title)) {
this.addLine("." + title);
}
this.addLine("|===");
String header = "|" + StringUtils.collectionToDelimitedString(Arrays.asList(columns), "|");
this.addLine(header);
this.addLine("");
this.addLine("|===");
}
@Override
public AsciidoctorTableCondition row(String... entries) {
for (String entry : entries) {
this.addLine(-1, "|" + escapeEntry(entry));
}
this.addLine(-1, "");
return this;
}
private String escapeEntry(String entry) {
if (entry.startsWith("`") && entry.endsWith("`")) {
return "`+" + entry.substring(1, entry.length() - 1) + "+`";
}
return entry;
}
@Override
public AsciidoctorTableCondition configuration(String configuration) {
this.addLine(0, configuration);
return this;
}
}
/**
* A {@link Condition} for a Markdown table.
*/
public static final class MarkdownTableCondition extends TableCondition<MarkdownTableCondition> {
private MarkdownTableCondition(String title, String... columns) {
if (StringUtils.hasText(title)) {
this.addLine(title);
this.addLine("");
}
String header = StringUtils.collectionToDelimitedString(Arrays.asList(columns), " | ");
this.addLine(header);
List<String> components = new ArrayList<>();
for (String column : columns) {
StringBuilder dashes = new StringBuilder();
for (int i = 0; i < column.length(); i++) {
dashes.append("-");
}
components.add(dashes.toString());
}
this.addLine(StringUtils.collectionToDelimitedString(components, " | "));
this.addLine("");
}
@Override
public MarkdownTableCondition row(String... entries) {
this.addLine(-1, StringUtils.collectionToDelimitedString(Arrays.asList(entries), " | "));
return this;
}
@Override
public MarkdownTableCondition configuration(String configuration) {
throw new UnsupportedOperationException("Markdown does not support table configuration");
}
}
}