Skip to content

Commit d2c7b0c

Browse files
authored
Modernize allure-spring-web for current Spring HTTP clients (via #1263)
1 parent bee34be commit d2c7b0c

File tree

4 files changed

+147
-40
lines changed

4 files changed

+147
-40
lines changed

README.md

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ https://github.com/SeleniumHQ/selenium/wiki/Logging
7878
```
7979

8080

81-
## Rest Assured
82-
83-
Filter for rest-assured http client, that generates attachment for allure.
81+
## Rest Assured
82+
83+
Filter for rest-assured http client, that generates attachment for allure.
8484

8585
```xml
8686
<dependency>
@@ -96,14 +96,50 @@ Usage example:
9696
```
9797
You can specify custom templates, which should be placed in src/main/resources/tpl folder:
9898
```
99-
.filter(new AllureRestAssured()
100-
.withRequestTemplate("custom-http-request.ftl")
101-
.withResponseTemplate("custom-http-response.ftl"))
102-
```
103-
104-
## OkHttp
105-
106-
Interceptor for OkHttp client, that generates attachment for allure.
99+
.filter(new AllureRestAssured()
100+
.withRequestTemplate("custom-http-request.ftl")
101+
.withResponseTemplate("custom-http-response.ftl"))
102+
```
103+
104+
## Spring Web
105+
106+
Interceptor for Spring synchronous HTTP clients, that generates attachments for allure.
107+
108+
```xml
109+
<dependency>
110+
<groupId>io.qameta.allure</groupId>
111+
<artifactId>allure-spring-web</artifactId>
112+
<version>$LATEST_VERSION</version>
113+
</dependency>
114+
```
115+
116+
Usage example with `RestClient`:
117+
```
118+
RestClient restClient = RestClient.builder()
119+
.requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
120+
.requestInterceptor(new AllureRestTemplate())
121+
.build();
122+
```
123+
Use a buffering request factory when the client should still be able to read the response body after Allure captures it.
124+
125+
`RestTemplate` remains supported:
126+
```
127+
RestTemplate restTemplate = new RestTemplate(
128+
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory())
129+
);
130+
restTemplate.setInterceptors(Collections.singletonList(new AllureRestTemplate()));
131+
```
132+
133+
You can specify custom templates, which should be placed in src/main/resources/tpl folder:
134+
```
135+
new AllureRestTemplate()
136+
.setRequestTemplate("custom-http-request.ftl")
137+
.setResponseTemplate("custom-http-response.ftl")
138+
```
139+
140+
## OkHttp
141+
142+
Interceptor for OkHttp client, that generates attachment for allure.
107143

108144
```xml
109145
<dependency>

allure-spring-web/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
description = "Allure Spring Web Integration"
22

3-
val springWebVersion = "6.2.12"
3+
val springWebVersion = "6.2.17"
44

55
dependencies {
66
api(project(":allure-attachments"))
@@ -32,4 +32,4 @@ tasks.test {
3232

3333
tasks.compileJava {
3434
options.release.set(17)
35-
}
35+
}

allure-spring-web/src/main/java/io/qameta/allure/springweb/AllureRestTemplate.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@
3636
import java.util.Map;
3737

3838
/**
39-
* Allure interceptor for spring rest template.
39+
* Allure interceptor for Spring synchronous HTTP clients such as
40+
* {@code RestTemplate} and {@code RestClient}.
41+
* <p>
42+
* Since this interceptor reads the response body to create an attachment,
43+
* configure a buffering request factory when the caller also needs to consume
44+
* the response body after interception.
4045
*/
4146
public class AllureRestTemplate implements ClientHttpRequestInterceptor {
4247

allure-spring-web/src/test/java/io/qameta/allure/springweb/AllureRestTemplateTest.java

Lines changed: 92 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333
import org.springframework.http.ResponseEntity;
3434
import org.springframework.http.client.BufferingClientHttpRequestFactory;
3535
import org.springframework.http.client.SimpleClientHttpRequestFactory;
36+
import org.springframework.web.client.RestClient;
3637
import org.springframework.web.client.RestTemplate;
3738

3839
import java.util.Collection;
3940
import java.util.Collections;
4041
import java.util.Objects;
42+
import java.util.concurrent.atomic.AtomicReference;
4143
import java.util.stream.Stream;
4244

4345
import static io.qameta.allure.test.RunUtils.runWithinTestContext;
@@ -49,55 +51,119 @@
4951
@SuppressWarnings("unchecked")
5052
public class AllureRestTemplateTest {
5153

52-
static Stream<String> attachmentNameProvider() {
53-
return Stream.of("Request", "Response");
54+
static Stream<SpringClientType> clientTypeProvider() {
55+
return Stream.of(SpringClientType.values());
5456
}
5557

5658
@ParameterizedTest
57-
@MethodSource(value = "attachmentNameProvider")
58-
void shouldCreateAttachment(final String attachmentName) {
59-
final AllureResults results = execute();
59+
@MethodSource("clientTypeProvider")
60+
void shouldCreateAttachment(final SpringClientType clientType) {
61+
final AllureResults results = execute(clientType).getAllureResults();
6062
assertThat(results.getTestResults())
6163
.flatExtracting(TestResult::getAttachments)
6264
.flatExtracting(Attachment::getName)
63-
.contains(attachmentName);
65+
.contains("Request", "Response");
6466
}
6567

6668
@ParameterizedTest
67-
@MethodSource(value = "attachmentNameProvider")
68-
void shouldCatchAttachmentBody(final String attachmentName) {
69-
final AllureResults results = execute();
69+
@MethodSource("clientTypeProvider")
70+
void shouldCatchAttachmentBody(final SpringClientType clientType) {
71+
final AllureResults results = execute(clientType).getAllureResults();
7072

71-
final Attachment found = results.getTestResults().stream()
72-
.map(TestResult::getAttachments)
73-
.flatMap(Collection::stream)
74-
.filter(attachment -> Objects.equals(attachmentName, attachment.getName()))
75-
.findAny()
76-
.orElseThrow(() -> new RuntimeException("attachment not found"));
77-
78-
assertThat(results.getAttachments())
79-
.containsKeys(found.getSource());
73+
Stream.of("Request", "Response")
74+
.map(attachmentName -> findAttachment(results, attachmentName))
75+
.forEach(found -> assertThat(results.getAttachments())
76+
.containsKeys(found.getSource()));
8077
}
8178

82-
protected final AllureResults execute() {
83-
final RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
84-
restTemplate.setInterceptors(Collections.singletonList(new AllureRestTemplate()));
79+
@ParameterizedTest
80+
@MethodSource("clientTypeProvider")
81+
void shouldAllowResponseBodyConsumptionAfterInterception(final SpringClientType clientType) {
82+
final ExecutionResult executionResult = execute(clientType);
83+
assertThat(executionResult.getResponse().getBody()).isEqualTo("some body");
84+
}
8585

86+
protected final ExecutionResult execute(final SpringClientType clientType) {
8687
final WireMockServer server = new WireMockServer(WireMockConfiguration.options().dynamicPort());
88+
final AtomicReference<ResponseEntity<String>> response = new AtomicReference<>();
8789

88-
return runWithinTestContext(() -> {
90+
final AllureResults results = runWithinTestContext(() -> {
8991
server.start();
9092
WireMock.configureFor(server.port());
9193
WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/hello")).willReturn(WireMock.aResponse().withBody("some body")));
9294
try {
93-
HttpHeaders headers = new HttpHeaders();
94-
headers.setContentType(MediaType.APPLICATION_JSON);
95-
HttpEntity<JsonNode> entity = new HttpEntity<>(headers);
96-
ResponseEntity<String> result = restTemplate.exchange(server.url("/hello"), HttpMethod.GET, entity, String.class);
95+
final ResponseEntity<String> result = clientType.execute(server.url("/hello"));
96+
response.set(result);
9797
Assertions.assertEquals(result.getStatusCode(), HttpStatus.OK);
98+
Assertions.assertEquals(result.getBody(), "some body");
9899
} finally {
99100
server.stop();
100101
}
101102
});
103+
104+
return new ExecutionResult(results, response.get());
105+
}
106+
107+
private static Attachment findAttachment(final AllureResults results, final String attachmentName) {
108+
return results.getTestResults().stream()
109+
.map(TestResult::getAttachments)
110+
.flatMap(Collection::stream)
111+
.filter(attachment -> Objects.equals(attachmentName, attachment.getName()))
112+
.findAny()
113+
.orElseThrow(() -> new RuntimeException("attachment not found"));
114+
}
115+
116+
private static BufferingClientHttpRequestFactory createBufferingRequestFactory() {
117+
return new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
118+
}
119+
120+
private enum SpringClientType {
121+
REST_TEMPLATE {
122+
@Override
123+
ResponseEntity<String> execute(final String url) {
124+
final RestTemplate restTemplate = new RestTemplate(createBufferingRequestFactory());
125+
restTemplate.setInterceptors(Collections.singletonList(new AllureRestTemplate()));
126+
127+
final HttpHeaders headers = new HttpHeaders();
128+
headers.setContentType(MediaType.APPLICATION_JSON);
129+
final HttpEntity<JsonNode> entity = new HttpEntity<>(headers);
130+
return restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
131+
}
132+
},
133+
REST_CLIENT {
134+
@Override
135+
ResponseEntity<String> execute(final String url) {
136+
final RestClient restClient = RestClient.builder()
137+
.requestFactory(createBufferingRequestFactory())
138+
.requestInterceptor(new AllureRestTemplate())
139+
.build();
140+
return restClient.get()
141+
.uri(url)
142+
.accept(MediaType.APPLICATION_JSON)
143+
.retrieve()
144+
.toEntity(String.class);
145+
}
146+
};
147+
148+
abstract ResponseEntity<String> execute(String url);
149+
}
150+
151+
private static final class ExecutionResult {
152+
153+
private final AllureResults allureResults;
154+
private final ResponseEntity<String> response;
155+
156+
private ExecutionResult(final AllureResults allureResults, final ResponseEntity<String> response) {
157+
this.allureResults = allureResults;
158+
this.response = response;
159+
}
160+
161+
AllureResults getAllureResults() {
162+
return allureResults;
163+
}
164+
165+
ResponseEntity<String> getResponse() {
166+
return response;
167+
}
102168
}
103169
}

0 commit comments

Comments
 (0)