Skip to content

Commit d41ced2

Browse files
committed
Add RSocketGraphQlTester
See gh-339
1 parent 8100650 commit d41ced2

11 files changed

+601
-52
lines changed

spring-graphql-test/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ dependencies {
1212
compileOnly 'org.springframework:spring-webflux'
1313
compileOnly 'org.springframework:spring-webmvc'
1414
compileOnly 'org.springframework:spring-websocket'
15+
compileOnly 'org.springframework:spring-messaging'
1516
compileOnly 'javax.servlet:javax.servlet-api'
17+
compileOnly 'io.rsocket:rsocket-core'
18+
compileOnly 'io.rsocket:rsocket-transport-netty'
1619
compileOnly 'org.skyscreamer:jsonassert'
1720
compileOnly 'com.google.code.findbugs:jsr305'
1821
compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
@@ -22,10 +25,12 @@ dependencies {
2225
testImplementation 'org.assertj:assertj-core'
2326
testImplementation 'org.mockito:mockito-core'
2427
testImplementation 'org.skyscreamer:jsonassert'
28+
testImplementation 'org.springframework:spring-messaging'
2529
testImplementation 'org.springframework:spring-webflux'
2630
testImplementation 'org.springframework:spring-test'
2731
testImplementation 'io.projectreactor:reactor-test'
2832
testImplementation 'io.projectreactor.netty:reactor-netty'
33+
testImplementation 'io.rsocket:rsocket-transport-local'
2934
testImplementation 'com.squareup.okhttp3:mockwebserver:3.14.9'
3035
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
3136

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/AbstractGraphQlTesterBuilder.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@
2424
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
2525
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
2626
import com.jayway.jsonpath.spi.mapper.MappingProvider;
27+
import reactor.core.publisher.Flux;
28+
import reactor.core.publisher.Mono;
2729

30+
import org.springframework.graphql.GraphQlRequest;
31+
import org.springframework.graphql.GraphQlResponse;
2832
import org.springframework.graphql.ResponseError;
2933
import org.springframework.graphql.client.AbstractGraphQlClientBuilder;
34+
import org.springframework.graphql.client.GraphQlClient;
3035
import org.springframework.graphql.client.GraphQlTransport;
3136
import org.springframework.graphql.support.CachingDocumentSource;
3237
import org.springframework.graphql.support.DocumentSource;
@@ -131,6 +136,36 @@ protected Consumer<AbstractGraphQlTesterBuilder<?>> getBuilderInitializer() {
131136
};
132137
}
133138

139+
/**
140+
* For cases where the Tester needs the {@link GraphQlTransport}, we can't use
141+
* transports directly since they are package private, but we can adapt the corresponding
142+
* {@link GraphQlClient} and adapt it to {@code GraphQlTransport}.
143+
*/
144+
protected static GraphQlTransport asTransport(GraphQlClient client) {
145+
return new GraphQlTransport() {
146+
147+
@Override
148+
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
149+
return client
150+
.document(request.getDocument())
151+
.operationName(request.getOperationName())
152+
.variables(request.getVariables())
153+
.execute()
154+
.cast(GraphQlResponse.class);
155+
}
156+
157+
@Override
158+
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
159+
return client
160+
.document(request.getDocument())
161+
.operationName(request.getOperationName())
162+
.variables(request.getVariables())
163+
.executeSubscription()
164+
.cast(GraphQlResponse.class);
165+
}
166+
};
167+
}
168+
134169

135170
private static class Jackson2Configurer {
136171

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlServiceTester.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class DefaultGraphQlServiceTester extends AbstractDelegatingGraphQlTester
3737
private final Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer;
3838

3939

40-
DefaultGraphQlServiceTester(GraphQlTester tester, GraphQlServiceGraphQlTransport transport,
40+
private DefaultGraphQlServiceTester(GraphQlTester tester, GraphQlServiceGraphQlTransport transport,
4141
Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer) {
4242

4343
super(tester);

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultHttpGraphQlTester.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ final class DefaultHttpGraphQlTester extends AbstractDelegatingGraphQlTester imp
4141
private final Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer;
4242

4343

44-
DefaultHttpGraphQlTester(GraphQlTester graphQlTester, WebTestClient webTestClient,
44+
private DefaultHttpGraphQlTester(GraphQlTester graphQlTester, WebTestClient webTestClient,
4545
Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer) {
4646

4747
super(graphQlTester);
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.test.tester;
18+
19+
import java.net.URI;
20+
import java.util.List;
21+
import java.util.function.Consumer;
22+
23+
import io.rsocket.transport.ClientTransport;
24+
25+
import org.springframework.core.codec.Decoder;
26+
import org.springframework.core.codec.Encoder;
27+
import org.springframework.graphql.client.RSocketGraphQlClient;
28+
import org.springframework.messaging.rsocket.RSocketRequester;
29+
import org.springframework.messaging.rsocket.RSocketStrategies;
30+
import org.springframework.util.MimeType;
31+
32+
33+
/**
34+
* Default {@link RSocketGraphQlTester} that builds and uses an
35+
* {@link RSocketGraphQlClient} for request execution.
36+
*
37+
* @author Rossen Stoyanchev
38+
* @since 1.0.0
39+
*/
40+
public class DefaultRSocketGraphQlTester extends AbstractDelegatingGraphQlTester implements RSocketGraphQlTester {
41+
42+
private final RSocketGraphQlClient rsocketGraphQlClient;
43+
44+
private final Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer;
45+
46+
47+
DefaultRSocketGraphQlTester(
48+
GraphQlTester delegate, RSocketGraphQlClient rsocketGraphQlClient,
49+
Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer) {
50+
51+
super(delegate);
52+
this.rsocketGraphQlClient = rsocketGraphQlClient;
53+
this.builderInitializer = builderInitializer;
54+
}
55+
56+
57+
@Override
58+
public RSocketGraphQlTester.Builder<?> mutate() {
59+
Builder builder = new Builder(this.rsocketGraphQlClient);
60+
this.builderInitializer.accept(builder);
61+
return builder;
62+
}
63+
64+
65+
/**
66+
* Default implementation of {@link GraphQlTester.Builder}.
67+
*/
68+
static final class Builder extends AbstractGraphQlTesterBuilder<Builder> implements RSocketGraphQlTester.Builder<Builder> {
69+
70+
private final RSocketGraphQlClient.Builder<?> rsocketGraphQlClientBuilder;
71+
72+
/**
73+
* Constructor to start via {@link RSocketGraphQlTester#builder()}.
74+
*/
75+
Builder() {
76+
this.rsocketGraphQlClientBuilder = RSocketGraphQlClient.builder();
77+
}
78+
79+
/**
80+
* Constructor to start via {@link RSocketGraphQlTester#builder(RSocketRequester.Builder)}.
81+
*/
82+
Builder(RSocketRequester.Builder requesterBuilder) {
83+
this.rsocketGraphQlClientBuilder = RSocketGraphQlClient.builder(requesterBuilder);
84+
}
85+
86+
/**
87+
* Constructor to mutate.
88+
* @param rsocketGraphQlClient the underlying client with the current state
89+
*/
90+
public Builder(RSocketGraphQlClient rsocketGraphQlClient) {
91+
this.rsocketGraphQlClientBuilder = rsocketGraphQlClient.mutate();
92+
}
93+
94+
@Override
95+
public Builder tcp(String host, int port) {
96+
this.rsocketGraphQlClientBuilder.tcp(host, port);
97+
return this;
98+
}
99+
100+
@Override
101+
public Builder webSocket(URI uri) {
102+
this.rsocketGraphQlClientBuilder.webSocket(uri);
103+
return this;
104+
}
105+
106+
@Override
107+
public Builder clientTransport(ClientTransport clientTransport) {
108+
this.rsocketGraphQlClientBuilder.clientTransport(clientTransport);
109+
return this;
110+
}
111+
112+
@Override
113+
public Builder dataMimeType(MimeType dataMimeType) {
114+
this.rsocketGraphQlClientBuilder.dataMimeType(dataMimeType);
115+
return this;
116+
}
117+
118+
@Override
119+
public Builder route(String route) {
120+
this.rsocketGraphQlClientBuilder.route(route);
121+
return this;
122+
}
123+
124+
@Override
125+
public Builder rsocketRequester(Consumer<RSocketRequester.Builder> requesterConsumer) {
126+
this.rsocketGraphQlClientBuilder.rsocketRequester(requesterConsumer);
127+
return this;
128+
}
129+
130+
@Override
131+
public RSocketGraphQlTester build() {
132+
registerJsonPathMappingProvider();
133+
RSocketGraphQlClient rsocketGraphQlClient = this.rsocketGraphQlClientBuilder.build();
134+
GraphQlTester graphQlTester = super.buildGraphQlTester(asTransport(rsocketGraphQlClient));
135+
return new DefaultRSocketGraphQlTester(graphQlTester, rsocketGraphQlClient, getBuilderInitializer());
136+
}
137+
138+
private void registerJsonPathMappingProvider() {
139+
this.rsocketGraphQlClientBuilder.rsocketRequester(builder ->
140+
builder.rsocketStrategies(strategiesBuilder ->
141+
configureJsonPathConfig(config -> {
142+
RSocketStrategies strategies = strategiesBuilder.build();
143+
List<Encoder<?>> encoders = strategies.encoders();
144+
List<Decoder<?>> decoders = strategies.decoders();
145+
return config.mappingProvider(new EncoderDecoderMappingProvider(encoders, decoders));
146+
})));
147+
}
148+
}
149+
150+
}

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final class DefaultWebGraphQlTester extends AbstractDelegatingGraphQlTester impl
4242
private final Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer;
4343

4444

45-
DefaultWebGraphQlTester(GraphQlTester tester, WebGraphQlHandlerGraphQlTransport transport,
45+
private DefaultWebGraphQlTester(GraphQlTester tester, WebGraphQlHandlerGraphQlTransport transport,
4646
Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer) {
4747

4848
super(tester);

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebSocketGraphQlTester.java

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,8 @@
2020
import java.net.URI;
2121
import java.util.function.Consumer;
2222

23-
import reactor.core.publisher.Flux;
2423
import reactor.core.publisher.Mono;
2524

26-
import org.springframework.graphql.GraphQlRequest;
27-
import org.springframework.graphql.GraphQlResponse;
28-
import org.springframework.graphql.client.GraphQlClient;
29-
import org.springframework.graphql.client.GraphQlTransport;
3025
import org.springframework.graphql.client.WebSocketGraphQlClient;
3126
import org.springframework.http.HttpHeaders;
3227
import org.springframework.http.codec.CodecConfigurer;
@@ -47,7 +42,7 @@ final class DefaultWebSocketGraphQlTester extends AbstractDelegatingGraphQlTeste
4742
private final Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer;
4843

4944

50-
DefaultWebSocketGraphQlTester(
45+
private DefaultWebSocketGraphQlTester(
5146
GraphQlTester graphQlTester, WebSocketGraphQlClient webSocketGraphQlClient,
5247
Consumer<AbstractGraphQlTesterBuilder<?>> builderInitializer) {
5348

@@ -154,36 +149,6 @@ private void registerJsonPathMappingProvider() {
154149
});
155150
});
156151
}
157-
158-
/**
159-
* GraphQlTransport implementations are private, but we can create the
160-
* GraphQlClient for it and adapt it.
161-
*/
162-
private static GraphQlTransport asTransport(GraphQlClient client) {
163-
return new GraphQlTransport() {
164-
165-
@Override
166-
public Mono<GraphQlResponse> execute(GraphQlRequest request) {
167-
return client
168-
.document(request.getDocument())
169-
.operationName(request.getOperationName())
170-
.variables(request.getVariables())
171-
.execute()
172-
.cast(GraphQlResponse.class);
173-
}
174-
175-
@Override
176-
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
177-
return client
178-
.document(request.getDocument())
179-
.operationName(request.getOperationName())
180-
.variables(request.getVariables())
181-
.executeSubscription()
182-
.cast(GraphQlResponse.class);
183-
}
184-
};
185-
}
186-
187152
}
188153

189154
}

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/EncoderDecoderMappingProvider.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919

2020
import java.util.Collections;
21+
import java.util.List;
2122
import java.util.Map;
23+
import java.util.stream.Stream;
2224

2325
import com.jayway.jsonpath.Configuration;
2426
import com.jayway.jsonpath.TypeRef;
@@ -56,31 +58,55 @@ final class EncoderDecoderMappingProvider implements MappingProvider {
5658

5759

5860
/**
59-
* Create an instance by finding the first JSON {@link Encoder} and
60-
* {@link Decoder} in the given {@link CodecConfigurer}.
61-
* @throws IllegalArgumentException if there is no JSON encoder or decoder.
61+
* Create an instance with a {@link CodecConfigurer}.
6262
*/
6363
public EncoderDecoderMappingProvider(CodecConfigurer configurer) {
6464
this.encoder = findJsonEncoder(configurer);
6565
this.decoder = findJsonDecoder(configurer);
6666
}
6767

68-
private static Decoder<?> findJsonDecoder(CodecConfigurer configurer) {
69-
return configurer.getReaders().stream()
70-
.filter((reader) -> reader.canRead(MAP_TYPE, MediaType.APPLICATION_JSON))
71-
.map((reader) -> ((DecoderHttpMessageReader<?>) reader).getDecoder())
72-
.findFirst()
73-
.orElseThrow(() -> new IllegalArgumentException("No JSON Decoder"));
68+
/**
69+
* Create an instance with a List of encoders and decoders>
70+
*/
71+
public EncoderDecoderMappingProvider(List<Encoder<?>> encoders, List<Decoder<?>> decoders) {
72+
this.encoder = findJsonEncoder(encoders);
73+
this.decoder = findJsonDecoder(decoders);
7474
}
7575

7676
private static Encoder<?> findJsonEncoder(CodecConfigurer configurer) {
77-
return configurer.getWriters().stream()
78-
.filter((writer) -> writer.canWrite(MAP_TYPE, MediaType.APPLICATION_JSON))
79-
.map((writer) -> ((EncoderHttpMessageWriter<?>) writer).getEncoder())
77+
return findJsonEncoder(configurer.getWriters().stream()
78+
.filter(writer -> writer instanceof EncoderHttpMessageWriter)
79+
.map(writer -> ((EncoderHttpMessageWriter<?>) writer).getEncoder()));
80+
}
81+
82+
private static Decoder<?> findJsonDecoder(CodecConfigurer configurer) {
83+
return findJsonDecoder(configurer.getReaders().stream()
84+
.filter(reader -> reader instanceof DecoderHttpMessageReader)
85+
.map(reader -> ((DecoderHttpMessageReader<?>) reader).getDecoder()));
86+
}
87+
88+
private static Encoder<?> findJsonEncoder(List<Encoder<?>> encoders) {
89+
return findJsonEncoder(encoders.stream());
90+
}
91+
92+
private static Decoder<?> findJsonDecoder(List<Decoder<?>> decoders) {
93+
return findJsonDecoder(decoders.stream());
94+
}
95+
96+
private static Encoder<?> findJsonEncoder(Stream<Encoder<?>> stream) {
97+
return stream
98+
.filter(encoder -> encoder.canEncode(MAP_TYPE, MediaType.APPLICATION_JSON))
8099
.findFirst()
81100
.orElseThrow(() -> new IllegalArgumentException("No JSON Encoder"));
82101
}
83102

103+
private static Decoder<?> findJsonDecoder(Stream<Decoder<?>> decoderStream) {
104+
return decoderStream
105+
.filter(decoder -> decoder.canDecode(MAP_TYPE, MediaType.APPLICATION_JSON))
106+
.findFirst()
107+
.orElseThrow(() -> new IllegalArgumentException("No JSON Decoder"));
108+
}
109+
84110

85111
@Nullable
86112
@Override

0 commit comments

Comments
 (0)