diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml new file mode 100644 index 00000000..a391a865 --- /dev/null +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 7d1c754d..3de6d5ac 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -1,8 +1,16 @@ + + + - + + + + + + diff --git a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolverTests.java b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolverTests.java index dafeb467..ebb7dc37 100644 --- a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolverTests.java +++ b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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,11 +23,10 @@ import java.util.Map; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link SnippetsDirectoryResolver}. @@ -39,9 +38,6 @@ public class SnippetsDirectoryResolverTests { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void mavenProjectsUseTargetGeneratedSnippetsRelativeToDocdir() throws IOException { this.temporaryFolder.newFile("pom.xml"); @@ -57,17 +53,15 @@ public class SnippetsDirectoryResolverTests { Map attributes = new HashMap<>(); String docdir = new File(this.temporaryFolder.getRoot(), "src/main/asciidoc").getAbsolutePath(); attributes.put("docdir", docdir); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage(equalTo("pom.xml not found in '" + docdir + "' or above")); - getMavenSnippetsDirectory(attributes); + assertThatIllegalStateException().isThrownBy(() -> getMavenSnippetsDirectory(attributes)) + .withMessage("pom.xml not found in '" + docdir + "' or above"); } @Test public void illegalStateWhenDocdirAttributeIsNotSetInMavenProject() throws IOException { Map attributes = new HashMap<>(); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage(equalTo("docdir attribute not found")); - getMavenSnippetsDirectory(attributes); + assertThatIllegalStateException().isThrownBy(() -> getMavenSnippetsDirectory(attributes)) + .withMessage("docdir attribute not found"); } @Test @@ -100,9 +94,9 @@ public class SnippetsDirectoryResolverTests { @Test public void illegalStateWhenGradleProjectdirAndProjectdirAttributesAreNotSetInGradleProject() throws IOException { Map attributes = new HashMap<>(); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage(equalTo("projectdir attribute not found")); - new SnippetsDirectoryResolver().getSnippetsDirectory(attributes); + assertThatIllegalStateException() + .isThrownBy(() -> new SnippetsDirectoryResolver().getSnippetsDirectory(attributes)) + .withMessage("projectdir attribute not found"); } private File getMavenSnippetsDirectory(Map attributes) { diff --git a/spring-restdocs-core/build.gradle b/spring-restdocs-core/build.gradle index 0cb5e2f4..fb5658ec 100644 --- a/spring-restdocs-core/build.gradle +++ b/spring-restdocs-core/build.gradle @@ -63,8 +63,6 @@ dependencies { testImplementation("org.assertj:assertj-core") testImplementation("org.javamoney:moneta") testImplementation("org.mockito:mockito-core") - testImplementation("org.hamcrest:hamcrest-core") - testImplementation("org.hamcrest:hamcrest-library") testImplementation("org.springframework:spring-test") testRuntimeOnly("org.glassfish:jakarta.el:4.0.2") diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetFailureTests.java index c6a5ee65..e8e16867 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -21,14 +21,12 @@ import java.util.Arrays; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.endsWith; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; /** @@ -42,25 +40,21 @@ public class RequestHeadersSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void missingRequestHeader() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - equalTo("Headers with the following names were not found" + " in the request: [Accept]")); - new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one"))) - .document(this.operationBuilder.request("http://localhost").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one"))) + .document(this.operationBuilder.request("http://localhost").build())) + .withMessage("Headers with the following names were not found in the request: [Accept]"); } @Test public void undocumentedRequestHeaderAndMissingRequestHeader() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - endsWith("Headers with the following names were not found" + " in the request: [Accept]")); - new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one"))) - .document(this.operationBuilder.request("http://localhost").header("X-Test", "test").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one"))) + .document(this.operationBuilder.request("http://localhost").header("X-Test", "test").build())) + .withMessageEndingWith("Headers with the following names were not found in the request: [Accept]"); + } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetFailureTests.java index 7e44f079..e2105de0 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -21,14 +21,12 @@ import java.util.Arrays; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.endsWith; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; /** @@ -42,25 +40,21 @@ public class ResponseHeadersSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void missingResponseHeader() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - equalTo("Headers with the following names were not found" + " in the response: [Content-Type]")); - new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one"))) - .document(this.operationBuilder.response().build()); + assertThatExceptionOfType(SnippetException.class).isThrownBy( + () -> new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one"))) + .document(this.operationBuilder.response().build())) + .withMessage("Headers with the following names were not found" + " in the response: [Content-Type]"); } @Test public void undocumentedResponseHeaderAndMissingResponseHeader() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - endsWith("Headers with the following names were not found" + " in the response: [Content-Type]")); - new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one"))) - .document(this.operationBuilder.response().header("X-Test", "test").build()); + assertThatExceptionOfType(SnippetException.class).isThrownBy( + () -> new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one"))) + .document(this.operationBuilder.response().header("X-Test", "test").build())) + .withMessageEndingWith( + "Headers with the following names were not found in the response: [Content-Type]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetFailureTests.java index 69796397..a10ba150 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -22,13 +22,12 @@ import java.util.Collections; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for failures when rendering {@link LinksSnippet} due to missing or undocumented @@ -41,42 +40,40 @@ public class LinksSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void undocumentedLink() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Links with the following relations were not" + " documented: [foo]")); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")), - Collections.emptyList()).document(this.operationBuilder.build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")), + Collections.emptyList()).document(this.operationBuilder.build())) + .withMessage("Links with the following relations were not documented: [foo]"); } @Test public void missingLink() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - equalTo("Links with the following relations were not" + " found in the response: [foo]")); - new LinksSnippet(new StubLinkExtractor(), Arrays.asList(new LinkDescriptor("foo").description("bar"))) - .document(this.operationBuilder.build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new LinksSnippet(new StubLinkExtractor(), + Arrays.asList(new LinkDescriptor("foo").description("bar"))) + .document(this.operationBuilder.build())) + .withMessage("Links with the following relations were not found in the response: [foo]"); } @Test public void undocumentedLinkAndMissingLink() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Links with the following relations were not" - + " documented: [a]. Links with the following relations were not" + " found in the response: [foo]")); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha")), - Arrays.asList(new LinkDescriptor("foo").description("bar"))).document(this.operationBuilder.build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha")), + Arrays.asList(new LinkDescriptor("foo").description("bar"))) + .document(this.operationBuilder.build())) + .withMessage("Links with the following relations were not documented: [a]. Links with the following" + + " relations were not found in the response: [foo]"); } @Test public void linkWithNoDescription() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("No description was provided for the link with rel 'foo' and no" - + " title was available from the link in the payload")); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")), - Arrays.asList(new LinkDescriptor("foo"))).document(this.operationBuilder.build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")), + Arrays.asList(new LinkDescriptor("foo"))).document(this.operationBuilder.build())) + .withMessage("No description was provided for the link with rel 'foo' and no title was available" + + " from the link in the payload"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/QueryStringParserTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/QueryStringParserTests.java index b287d441..0cb99f12 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/QueryStringParserTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/QueryStringParserTests.java @@ -19,12 +19,10 @@ package org.springframework.restdocs.operation; import java.net.URI; import java.util.Arrays; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link QueryStringParser}. @@ -33,9 +31,6 @@ import static org.hamcrest.CoreMatchers.equalTo; */ public class QueryStringParserTests { - @Rule - public ExpectedException thrown = ExpectedException.none(); - private final QueryStringParser queryStringParser = new QueryStringParser(); @Test @@ -76,9 +71,9 @@ public class QueryStringParserTests { @Test public void malformedParameter() { - this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage(equalTo("The parameter 'a=apple=avocado' is malformed")); - this.queryStringParser.parse(URI.create("http://localhost?a=apple=avocado")); + assertThatIllegalArgumentException() + .isThrownBy(() -> this.queryStringParser.parse(URI.create("http://localhost?a=apple=avocado"))) + .withMessage("The parameter 'a=apple=avocado' is malformed"); } @Test diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java index 86e030d4..adc34708 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java @@ -23,10 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Rule; import org.junit.Test; -import org.springframework.restdocs.testfixtures.OutputCapture; +import org.springframework.restdocs.testfixtures.OutputCaptureRule; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.isEmptyString; /** * Tests for {@link PrettyPrintingContentModifier}. @@ -37,7 +36,7 @@ import static org.hamcrest.Matchers.isEmptyString; public class PrettyPrintingContentModifierTests { @Rule - public OutputCapture outputCapture = new OutputCapture(); + public OutputCaptureRule outputCapture = new OutputCaptureRule(); @Test public void prettyPrintJson() throws Exception { @@ -61,17 +60,17 @@ public class PrettyPrintingContentModifierTests { @Test public void nonJsonAndNonXmlContentIsHandledGracefully() throws Exception { String content = "abcdefg"; - this.outputCapture.expect(isEmptyString()); assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null)) .isEqualTo(content.getBytes()); + assertThat(this.outputCapture).isEmpty(); } @Test public void nonJsonContentThatInitiallyLooksLikeJsonIsHandledGracefully() throws Exception { String content = "\"abc\",\"def\""; - this.outputCapture.expect(isEmptyString()); assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null)) .isEqualTo(content.getBytes()); + assertThat(this.outputCapture).isEmpty(); } @Test diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java index 99d6ed5e..e9d2f38c 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -22,7 +22,6 @@ import java.util.Collections; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -30,9 +29,7 @@ import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.endsWith; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.startsWith; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; /** @@ -46,148 +43,145 @@ public class RequestFieldsSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void undocumentedRequestField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not" + " documented:")); - new RequestFieldsSnippet(Collections.emptyList()) - .document(this.operationBuilder.request("http://localhost").content("{\"a\": 5}").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Collections.emptyList()) + .document(this.operationBuilder.request("http://localhost").content("{\"a\": 5}").build())) + .withMessageStartingWith("The following parts of the payload were not documented:"); } @Test public void missingRequestField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Fields with the following paths were not found" + " in the payload: [a.b]")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) - .document(this.operationBuilder.request("http://localhost").content("{}").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) + .document(this.operationBuilder.request("http://localhost").content("{}").build())) + .withMessage("Fields with the following paths were not found in the payload: [a.b]"); } @Test public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException { - this.thrown.expect(FieldTypeRequiredException.class); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").optional())) - .document(this.operationBuilder.request("http://localhost").content("{ }").build()); + assertThatExceptionOfType(FieldTypeRequiredException.class).isThrownBy( + () -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").optional())) + .document(this.operationBuilder.request("http://localhost").content("{ }").build())); } @Test public void undocumentedRequestFieldAndMissingRequestField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not" + " documented:")); - this.thrown - .expectMessage(endsWith("Fields with the following paths were not found" + " in the payload: [a.b]")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) - .document(this.operationBuilder.request("http://localhost").content("{ \"a\": { \"c\": 5 }}").build()); + assertThatExceptionOfType(SnippetException.class).isThrownBy( + () -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))).document( + this.operationBuilder.request("http://localhost").content("{ \"a\": { \"c\": 5 }}").build())) + .withMessageStartingWith("The following parts of the payload were not documented:") + .withMessageEndingWith("Fields with the following paths were not found in the payload: [a.b]"); } @Test public void attemptToDocumentFieldsWithNoRequestBody() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Cannot document request fields as the request body is empty")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").build())) + .withMessage("Cannot document request fields as the request body is empty"); } @Test public void fieldWithExplicitTypeThatDoesNotMatchThePayload() throws IOException { - this.thrown.expect(FieldTypesDoNotMatchException.class); - this.thrown.expectMessage( - equalTo("The documented type of the field 'a' is" + " Object but the actual type is Number")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))) - .document(this.operationBuilder.request("http://localhost").content("{ \"a\": 5 }").build()); + assertThatExceptionOfType(FieldTypesDoNotMatchException.class) + .isThrownBy(() -> new RequestFieldsSnippet( + Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))).document( + this.operationBuilder.request("http://localhost").content("{ \"a\": 5 }").build())) + .withMessage("The documented type of the field 'a' is Object but the actual type is Number"); } @Test public void fieldWithExplicitSpecificTypeThatActuallyVaries() throws IOException { - this.thrown.expect(FieldTypesDoNotMatchException.class); - this.thrown.expectMessage( - equalTo("The documented type of the field '[].a' is" + " Object but the actual type is Varies")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))) - .document(this.operationBuilder.request("http://localhost").content("[{ \"a\": 5 },{ \"a\": \"b\" }]") - .build()); + assertThatExceptionOfType(FieldTypesDoNotMatchException.class) + .isThrownBy(() -> new RequestFieldsSnippet( + Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))) + .document(this.operationBuilder.request("http://localhost") + .content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build())) + .withMessage("The documented type of the field '[].a' is Object but the actual type is Varies"); } @Test public void undocumentedXmlRequestField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not documented:")); - new RequestFieldsSnippet(Collections.emptyList()) - .document(this.operationBuilder.request("http://localhost").content("5") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Collections.emptyList()) + .document(this.operationBuilder.request("http://localhost").content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())) + .withMessageStartingWith("The following parts of the payload were not documented:"); } @Test public void xmlDescendentsAreNotDocumentedByFieldDescriptor() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not documented:")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").type("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").content("5") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy( + () -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").type("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())) + .withMessageStartingWith("The following parts of the payload were not documented:"); } @Test public void xmlRequestFieldWithNoType() throws IOException { - this.thrown.expect(FieldTypeRequiredException.class); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").content("5") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(FieldTypeRequiredException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())); } @Test public void missingXmlRequestField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Fields with the following paths were not found" + " in the payload: [a/b]")); - new RequestFieldsSnippet( - Arrays.asList(fieldWithPath("a/b").description("one"), fieldWithPath("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").content("") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet( + Arrays.asList(fieldWithPath("a/b").description("one"), fieldWithPath("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").content("") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())) + .withMessage("Fields with the following paths were not found in the payload: [a/b]"); } @Test public void undocumentedXmlRequestFieldAndMissingXmlRequestField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not documented:")); - this.thrown - .expectMessage(endsWith("Fields with the following paths were not found" + " in the payload: [a/b]")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"))) - .document(this.operationBuilder.request("http://localhost").content("5") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"))) + .document(this.operationBuilder.request("http://localhost").content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())) + .withMessageStartingWith("The following parts of the payload were not documented:") + .withMessageEndingWith("Fields with the following paths were not found in the payload: [a/b]"); } @Test public void unsupportedContent() throws IOException { - this.thrown.expect(PayloadHandlingException.class); - this.thrown.expectMessage( - equalTo("Cannot handle text/plain content as it could" + " not be parsed as JSON or XML")); - new RequestFieldsSnippet(Collections.emptyList()) - .document(this.operationBuilder.request("http://localhost").content("Some plain text") - .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE).build()); + assertThatExceptionOfType(PayloadHandlingException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Collections.emptyList()) + .document(this.operationBuilder.request("http://localhost").content("Some plain text") + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE).build())) + .withMessage("Cannot handle text/plain content as it could not be parsed as JSON or XML"); } @Test public void nonOptionalFieldBeneathArrayThatIsSometimesNull() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - startsWith("Fields with the following paths were not found in the payload: " + "[a[].b]")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER), - fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) - .document(this.operationBuilder.request("http://localhost").content( - "{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"b\": null, \"c\": 2}," + " {\"b\": 1,\"c\": 2}]}") - .build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet( + Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER), + fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) + .document(this.operationBuilder.request("http://localhost") + .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"b\": null, \"c\": 2}," + + " {\"b\": 1,\"c\": 2}]}") + .build())) + .withMessageStartingWith("Fields with the following paths were not found in the payload: [a[].b]"); } @Test public void nonOptionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - startsWith("Fields with the following paths were not found in the payload: " + "[a[].b]")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER), - fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) - .document(this.operationBuilder.request("http://localhost") - .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}") - .build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet( + Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER), + fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) + .document(this.operationBuilder.request("http://localhost") + .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}") + .build())) + .withMessageStartingWith("Fields with the following paths were not found in the payload: [a[].b]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java index 61cde5fb..2226e8c1 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -22,14 +22,12 @@ import java.util.Collections; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.startsWith; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; /** @@ -44,32 +42,32 @@ public class RequestPartFieldsSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void undocumentedRequestPartField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not documented:")); - new RequestPartFieldsSnippet("part", Collections.emptyList()).document( - this.operationBuilder.request("http://localhost").part("part", "{\"a\": 5}".getBytes()).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestPartFieldsSnippet("part", Collections.emptyList()) + .document(this.operationBuilder.request("http://localhost") + .part("part", "{\"a\": 5}".getBytes()).build())) + .withMessageStartingWith("The following parts of the payload were not documented:"); } @Test public void missingRequestPartField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not documented:")); - new RequestPartFieldsSnippet("part", Arrays.asList(fieldWithPath("b").description("one"))).document( - this.operationBuilder.request("http://localhost").part("part", "{\"a\": 5}".getBytes()).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy( + () -> new RequestPartFieldsSnippet("part", Arrays.asList(fieldWithPath("b").description("one"))) + .document(this.operationBuilder.request("http://localhost") + .part("part", "{\"a\": 5}".getBytes()).build())) + .withMessageStartingWith("The following parts of the payload were not documented:"); } @Test public void missingRequestPart() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("A request part named 'another' was not found in the request")); - new RequestPartFieldsSnippet("another", Arrays.asList(fieldWithPath("a.b").description("one"))) - .document(this.operationBuilder.request("http://localhost") - .part("part", "{\"a\": {\"b\": 5}}".getBytes()).build()); + assertThatExceptionOfType(SnippetException.class).isThrownBy( + () -> new RequestPartFieldsSnippet("another", Arrays.asList(fieldWithPath("a.b").description("one"))) + .document(this.operationBuilder.request("http://localhost") + .part("part", "{\"a\": {\"b\": 5}}".getBytes()).build())) + .withMessage("A request part named 'another' was not found in the request"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java index fa33bebc..4329bdcf 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -22,7 +22,6 @@ import java.util.Collections; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -30,9 +29,7 @@ import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.endsWith; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.startsWith; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; /** @@ -46,124 +43,125 @@ public class ResponseFieldsSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void attemptToDocumentFieldsWithNoResponseBody() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Cannot document response fields as the response body is empty")); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) - .document(this.operationBuilder.build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) + .document(this.operationBuilder.build())) + .withMessage("Cannot document response fields as the response body is empty"); } @Test public void fieldWithExplicitTypeThatDoesNotMatchThePayload() throws IOException { - this.thrown.expect(FieldTypesDoNotMatchException.class); - this.thrown.expectMessage( - equalTo("The documented type of the field 'a' is" + " Object but the actual type is Number")); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))) - .document(this.operationBuilder.response().content("{ \"a\": 5 }}").build()); + assertThatExceptionOfType(FieldTypesDoNotMatchException.class) + .isThrownBy(() -> new ResponseFieldsSnippet( + Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))) + .document(this.operationBuilder.response().content("{ \"a\": 5 }}").build())) + .withMessage("The documented type of the field 'a' is Object but the actual type is Number"); } @Test public void fieldWithExplicitSpecificTypeThatActuallyVaries() throws IOException { - this.thrown.expect(FieldTypesDoNotMatchException.class); - this.thrown.expectMessage( - equalTo("The documented type of the field '[].a' is" + " Object but the actual type is Varies")); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))) - .document(this.operationBuilder.response().content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build()); + assertThatExceptionOfType(FieldTypesDoNotMatchException.class) + .isThrownBy(() -> new ResponseFieldsSnippet( + Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))).document( + this.operationBuilder.response().content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build())) + .withMessage("The documented type of the field '[].a' is Object but the actual type is Varies"); } @Test public void undocumentedXmlResponseField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not" + " documented:")); - new ResponseFieldsSnippet(Collections.emptyList()).document(this.operationBuilder.response() - .content("5").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new ResponseFieldsSnippet(Collections.emptyList()) + .document(this.operationBuilder.response().content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())) + .withMessageStartingWith("The following parts of the payload were not documented:"); } @Test public void missingXmlAttribute() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown - .expectMessage(equalTo("Fields with the following paths were not found" + " in the payload: [a/@id]")); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"), - fieldWithPath("a/@id").description("two").type("c"))) - .document(this.operationBuilder.response().content("foo") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy( + () -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"), + fieldWithPath("a/@id").description("two").type("c"))) + .document( + this.operationBuilder.response().content("foo") + .header(HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_XML_VALUE) + .build())) + .withMessage("Fields with the following paths were not found in the payload: [a/@id]"); } @Test public void documentedXmlAttributesAreRemoved() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - equalTo(String.format("The following parts of the payload were not documented:" + "%nbar%n"))); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/@id").description("one").type("a"))) - .document(this.operationBuilder.response().content("bar") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class).isThrownBy( + () -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/@id").description("one").type("a"))) + .document(this.operationBuilder.response().content("bar") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())) + .withMessage(String.format("The following parts of the payload were not documented:%nbar%n")); } @Test public void xmlResponseFieldWithNoType() throws IOException { - this.thrown.expect(FieldTypeRequiredException.class); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) - .document(this.operationBuilder.response().content("5") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(FieldTypeRequiredException.class) + .isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) + .document(this.operationBuilder.response().content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())); } @Test public void missingXmlResponseField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Fields with the following paths were not found" + " in the payload: [a/b]")); - new ResponseFieldsSnippet( - Arrays.asList(fieldWithPath("a/b").description("one"), fieldWithPath("a").description("one"))) - .document(this.operationBuilder.response().content("") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new ResponseFieldsSnippet( + Arrays.asList(fieldWithPath("a/b").description("one"), fieldWithPath("a").description("one"))) + .document(this.operationBuilder.response().content("") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())) + .withMessage("Fields with the following paths were not found in the payload: [a/b]"); } @Test public void undocumentedXmlResponseFieldAndMissingXmlResponseField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith("The following parts of the payload were not" + " documented:")); - this.thrown - .expectMessage(endsWith("Fields with the following paths were not found" + " in the payload: [a/b]")); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"))) - .document(this.operationBuilder.response().content("5") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"))) + .document(this.operationBuilder.response().content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build())) + .withMessageStartingWith("The following parts of the payload were not documented:") + .withMessageEndingWith("Fields with the following paths were not found in the payload: [a/b]"); } @Test public void unsupportedContent() throws IOException { - this.thrown.expect(PayloadHandlingException.class); - this.thrown.expectMessage( - equalTo("Cannot handle text/plain content as it could" + " not be parsed as JSON or XML")); - new ResponseFieldsSnippet(Collections.emptyList()).document(this.operationBuilder.response() - .content("Some plain text").header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE).build()); + assertThatExceptionOfType(PayloadHandlingException.class) + .isThrownBy(() -> new ResponseFieldsSnippet(Collections.emptyList()) + .document(this.operationBuilder.response().content("Some plain text") + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE).build())) + .withMessage("Cannot handle text/plain content as it could not be parsed as JSON or XML"); } @Test public void nonOptionalFieldBeneathArrayThatIsSometimesNull() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - startsWith("Fields with the following paths were not found in the payload: " + "[a[].b]")); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER), - fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) - .document(this.operationBuilder.response().content( - "{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"b\": null, \"c\": 2}," + " {\"b\": 1,\"c\": 2}]}") - .build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new ResponseFieldsSnippet( + Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER), + fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) + .document(this.operationBuilder.response() + .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"b\": null, \"c\": 2}," + + " {\"b\": 1,\"c\": 2}]}") + .build())) + .withMessageStartingWith("Fields with the following paths were not found in the payload: [a[].b]"); } @Test public void nonOptionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - startsWith("Fields with the following paths were not found in the payload: " + "[a[].b]")); - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER), - fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) - .document(this.operationBuilder.response() - .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}") - .build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new ResponseFieldsSnippet( + Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER), + fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) + .document(this.operationBuilder.response() + .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}") + .build())) + .withMessageStartingWith("Fields with the following paths were not found in the payload: [a[].b]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetFailureTests.java index e43207d6..83176449 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetFailureTests.java @@ -22,14 +22,13 @@ import java.util.Collections; import org.junit.Rule; import org.junit.Test; -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.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; /** @@ -43,35 +42,33 @@ public class PathParametersSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void undocumentedPathParameter() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Path parameters with the following names were" + " not documented: [a]")); - new PathParametersSnippet(Collections.emptyList()).document(this.operationBuilder - .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new PathParametersSnippet(Collections.emptyList()) + .document(this.operationBuilder + .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/").build())) + .withMessage("Path parameters with the following names were not documented: [a]"); } @Test public void missingPathParameter() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - equalTo("Path parameters with the following names were" + " not found in the request: [a]")); - new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))).document( - this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder + .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/").build())) + .withMessage("Path parameters with the following names were not found in the request: [a]"); } @Test public void undocumentedAndMissingPathParameters() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Path parameters with the following names were" - + " not documented: [b]. Path parameters with the following" - + " names were not found in the request: [a]")); - new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) - .document(this.operationBuilder - .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{b}").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder + .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{b}").build())) + .withMessage( + "Path parameters with the following names were not documented: [b]. Path parameters with the" + + " following names were not found in the request: [a]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetFailureTests.java index 493babd6..929308e0 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -22,13 +22,12 @@ import java.util.Collections; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; /** @@ -42,34 +41,29 @@ public class RequestParametersSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void undocumentedParameter() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Request parameters with the following names were" + " not documented: [a]")); - new RequestParametersSnippet(Collections.emptyList()) - .document(this.operationBuilder.request("http://localhost").param("a", "alpha").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestParametersSnippet(Collections.emptyList()) + .document(this.operationBuilder.request("http://localhost").param("a", "alpha").build())) + .withMessage("Request parameters with the following names were not documented: [a]"); } @Test public void missingParameter() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - equalTo("Request parameters with the following names were" + " not found in the request: [a]")); - new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").build())) + .withMessage("Request parameters with the following names were not found in the request: [a]"); } @Test public void undocumentedAndMissingParameters() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Request parameters with the following names were" - + " not documented: [b]. Request parameters with the following" - + " names were not found in the request: [a]")); - new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").param("b", "bravo").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").param("b", "bravo").build())) + .withMessage("Request parameters with the following names were not documented: [b]. Request parameters" + + " with the following names were not found in the request: [a]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetFailureTests.java index c937499b..64b4b0e5 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -22,13 +22,12 @@ import java.util.Collections; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.request.RequestDocumentation.partWithName; /** @@ -42,34 +41,29 @@ public class RequestPartsSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Test public void undocumentedPart() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo("Request parts with the following names were" + " not documented: [a]")); - new RequestPartsSnippet(Collections.emptyList()) - .document(this.operationBuilder.request("http://localhost").part("a", "alpha".getBytes()).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestPartsSnippet(Collections.emptyList()).document( + this.operationBuilder.request("http://localhost").part("a", "alpha".getBytes()).build())) + .withMessage("Request parts with the following names were not documented: [a]"); } @Test public void missingPart() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage( - equalTo("Request parts with the following names were" + " not found in the request: [a]")); - new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").build())) + .withMessage("Request parts with the following names were not found in the request: [a]"); } @Test public void undocumentedAndMissingParts() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(equalTo( - "Request parts with the following names were" + " not documented: [b]. Request parts with the following" - + " names were not found in the request: [a]")); - new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build()); + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one"))).document( + this.operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build())) + .withMessage("Request parts with the following names were not documented: [b]. Request parts with the" + + " following names were not found in the request: [a]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java index 3b3c5e9d..2782b488 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -21,14 +21,12 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.springframework.core.io.Resource; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link TemplateResourceResolver}. @@ -37,9 +35,6 @@ import static org.hamcrest.CoreMatchers.equalTo; */ public class StandardTemplateResourceResolverTests { - @Rule - public final ExpectedException thrown = ExpectedException.none(); - private final TemplateResourceResolver resolver = new StandardTemplateResourceResolver( TemplateFormats.asciidoctor()); @@ -101,16 +96,10 @@ public class StandardTemplateResourceResolverTests { @Test public void failsIfCustomAndDefaultSnippetsDoNotExist() throws Exception { - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage(equalTo("Template named 'test' could not be resolved")); - doWithThreadContextClassLoader(this.classLoader, new Callable() { - - @Override - public Resource call() { - return StandardTemplateResourceResolverTests.this.resolver.resolveTemplateResource("test"); - } - - }); + assertThatIllegalStateException() + .isThrownBy(() -> doWithThreadContextClassLoader(this.classLoader, + () -> StandardTemplateResourceResolverTests.this.resolver.resolveTemplateResource("test"))) + .withMessage("Template named 'test' could not be resolved"); } private T doWithThreadContextClassLoader(ClassLoader classLoader, Callable action) throws Exception { diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/CapturedOutput.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/CapturedOutput.java new file mode 100644 index 00000000..699864f5 --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/CapturedOutput.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014-2022 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.testfixtures; + +/** + * Provides access to {@link System#out System.out} and {@link System#err System.err} + * output that has been captured by the {@link OutputCaptureRule}. Can be used to apply + * assertions using AssertJ. For example:
+ * assertThat(output).contains("started"); // Checks all output
+ * assertThat(output.getErr()).contains("failed"); // Only checks System.err
+ * assertThat(output.getOut()).contains("ok"); // Only checks System.out
+ * 
+ * + * @author Madhura Bhave + * @author Phillip Webb + * @author Andy Wilkinson + */ +public interface CapturedOutput extends CharSequence { + + @Override + default int length() { + return toString().length(); + } + + @Override + default char charAt(int index) { + return toString().charAt(index); + } + + @Override + default CharSequence subSequence(int start, int end) { + return toString().subSequence(start, end); + } + + /** + * Return all content (both {@link System#out System.out} and {@link System#err + * System.err}) in the order that it was captured. + * @return all captured output + */ + String getAll(); + + /** + * Return {@link System#out System.out} content in the order that it was captured. + * @return {@link System#out System.out} captured output + */ + String getOut(); + + /** + * Return {@link System#err System.err} content in the order that it was captured. + * @return {@link System#err System.err} captured output + */ + String getErr(); + +} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCapture.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCapture.java index d8c86ed6..eef8d6a3 100644 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCapture.java +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCapture.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -16,139 +16,257 @@ package org.springframework.restdocs.testfixtures; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; -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; +import org.springframework.util.Assert; /** - * JUnit {@code @Rule} to capture output from System.out and System.err. + * Provides support for capturing {@link System#out System.out} and {@link System#err + * System.err}. * + * @author Madhura Bhave * @author Phillip Webb * @author Andy Wilkinson + * @author Sam Brannen + * @see OutputCaptureRule */ -public class OutputCapture implements TestRule { +class OutputCapture implements CapturedOutput { - private CaptureOutputStream captureOut; + private final Deque systemCaptures = new ArrayDeque<>(); - private CaptureOutputStream captureErr; - - private ByteArrayOutputStream capturedOutput; - - private List> 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 - } + /** + * Push a new system capture session onto the stack. + */ + final void push() { + this.systemCaptures.addLast(new SystemCapture()); } /** - * Verify that the output is matched by the supplied {@code matcher}. Verification is - * performed after the test method has executed. - * @param matcher the matcher + * Pop the last system capture session from the stack. */ - public final void expect(Matcher matcher) { - this.matchers.add(matcher); + final void pop() { + this.systemCaptures.removeLast().release(); } - private static final class CaptureOutputStream extends OutputStream { + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof CapturedOutput || obj instanceof CharSequence) { + return getAll().equals(obj.toString()); + } + return false; + } - private final PrintStream original; + @Override + public int hashCode() { + return toString().hashCode(); + } - private final OutputStream copy; + @Override + public String toString() { + return getAll(); + } - private CaptureOutputStream(PrintStream original, OutputStream copy) { - this.original = original; + /** + * Return all content (both {@link System#out System.out} and {@link System#err + * System.err}) in the order that it was captured. + * @return all captured output + */ + @Override + public String getAll() { + return get((type) -> true); + } + + /** + * Return {@link System#out System.out} content in the order that it was captured. + * @return {@link System#out System.out} captured output + */ + @Override + public String getOut() { + return get(Type.OUT::equals); + } + + /** + * Return {@link System#err System.err} content in the order that it was captured. + * @return {@link System#err System.err} captured output + */ + @Override + public String getErr() { + return get(Type.ERR::equals); + } + + /** + * Resets the current capture session, clearing its captured output. + */ + void reset() { + this.systemCaptures.peek().reset(); + } + + private String get(Predicate filter) { + Assert.state(!this.systemCaptures.isEmpty(), + "No system captures found. Please check your output capture registration."); + StringBuilder builder = new StringBuilder(); + for (SystemCapture systemCapture : this.systemCaptures) { + systemCapture.append(builder, filter); + } + return builder.toString(); + } + + /** + * A capture session that captures {@link System#out System.out} and {@link System#out + * System.err}. + */ + private static class SystemCapture { + + private final Object monitor = new Object(); + + private final PrintStreamCapture out; + + private final PrintStreamCapture err; + + private final List capturedStrings = new ArrayList<>(); + + SystemCapture() { + this.out = new PrintStreamCapture(System.out, this::captureOut); + this.err = new PrintStreamCapture(System.err, this::captureErr); + System.setOut(this.out); + System.setErr(this.err); + } + + void release() { + System.setOut(this.out.getParent()); + System.setErr(this.err.getParent()); + } + + private void captureOut(String string) { + synchronized (this.monitor) { + this.capturedStrings.add(new CapturedString(Type.OUT, string)); + } + } + + private void captureErr(String string) { + synchronized (this.monitor) { + this.capturedStrings.add(new CapturedString(Type.ERR, string)); + } + } + + void append(StringBuilder builder, Predicate filter) { + synchronized (this.monitor) { + for (CapturedString stringCapture : this.capturedStrings) { + if (filter.test(stringCapture.getType())) { + builder.append(stringCapture); + } + } + } + } + + void reset() { + synchronized (this.monitor) { + this.capturedStrings.clear(); + } + } + + } + + /** + * A {@link PrintStream} implementation that captures written strings. + */ + private static class PrintStreamCapture extends PrintStream { + + private final PrintStream parent; + + PrintStreamCapture(PrintStream parent, Consumer copy) { + super(new OutputStreamCapture(getSystemStream(parent), copy)); + this.parent = parent; + } + + PrintStream getParent() { + return this.parent; + } + + private static PrintStream getSystemStream(PrintStream printStream) { + while (printStream instanceof PrintStreamCapture) { + printStream = ((PrintStreamCapture) printStream).getParent(); + } + return printStream; + } + + } + + /** + * An {@link OutputStream} implementation that captures written strings. + */ + private static class OutputStreamCapture extends OutputStream { + + private final PrintStream systemStream; + + private final Consumer copy; + + OutputStreamCapture(PrintStream systemStream, Consumer copy) { + this.systemStream = systemStream; 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); + write(new byte[] { (byte) (b & 0xFF) }); } @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; + this.copy.accept(new String(b, off, len)); + this.systemStream.write(b, off, len); } @Override public void flush() throws IOException { - this.copy.flush(); - this.original.flush(); + this.systemStream.flush(); } } + /** + * A captured string that forms part of the full output. + */ + private static class CapturedString { + + private final Type type; + + private final String string; + + CapturedString(Type type, String string) { + this.type = type; + this.string = string; + } + + Type getType() { + return this.type; + } + + @Override + public String toString() { + return this.string; + } + + } + + /** + * Types of content that can be captured. + */ + private enum Type { + + OUT, ERR + + } + } diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCaptureRule.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCaptureRule.java new file mode 100644 index 00000000..38710f25 --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCaptureRule.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014-2022 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.testfixtures; + +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * JUnit {@code @Rule} to capture output from System.out and System.err. + *

+ * To use add as a {@link Rule @Rule}: + * + *

+ * public class MyTest {
+ *
+ *     @Rule
+ *     public OutputCaptureRule output = new OutputCaptureRule();
+ *
+ *     @Test
+ *     public void test() {
+ *         assertThat(output).contains("ok");
+ *     }
+ *
+ * }
+ * 
+ * + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class OutputCaptureRule implements TestRule, CapturedOutput { + + private final OutputCapture delegate = new OutputCapture(); + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + OutputCaptureRule.this.delegate.push(); + try { + base.evaluate(); + } + finally { + OutputCaptureRule.this.delegate.pop(); + } + } + }; + } + + @Override + public String getAll() { + return this.delegate.getAll(); + } + + @Override + public String getOut() { + return this.delegate.getOut(); + } + + @Override + public String getErr() { + return this.delegate.getErr(); + } + + @Override + public String toString() { + return this.delegate.toString(); + } + +}