examples: unit test examples for users

Demonstrate unit testing gRPC clients and servers with `InProcessTransport`.
This commit is contained in:
ZHANG Dapeng 2016-11-15 14:15:55 -08:00 committed by GitHub
parent 7311572236
commit 7306df4266
18 changed files with 1469 additions and 43 deletions

View File

@ -44,3 +44,23 @@ $ mvn exec:java -Dexec.mainClass=io.grpc.examples.helloworld.HelloWorldServer
$ # In another terminal run the client
$ mvn exec:java -Dexec.mainClass=io.grpc.examples.helloworld.HelloWorldClient
```
Unit test examples
==============================================
Examples for unit testing gRPC clients and servers are located in [examples/src/test](src/test).
In general, we DO NOT allow overriding the client stub.
We encourage users to leverage `InProcessTransport` as demonstrated in the examples to
write unit tests. `InProcessTransport` is light-weight and runs the server
and client in the same process without any socket/TCP connection.
For testing a gRPC client, create the client with a real stub
using an
[InProcessChannel](../core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java),
and test it against an
[InProcessServer](../core/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java)
with a mock/fake service implementation.
For testing a gRPC server, create the server as an InProcessServer,
and test it against a real client stub with an InProcessChannel.

View File

@ -28,6 +28,9 @@ dependencies {
compile "io.grpc:grpc-netty:${grpcVersion}"
compile "io.grpc:grpc-protobuf:${grpcVersion}"
compile "io.grpc:grpc-stub:${grpcVersion}"
testCompile "junit:junit:4.11"
testCompile "org.mockito:mockito-core:1.9.5"
}
protobuf {

View File

@ -28,6 +28,18 @@
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<extensions>

View File

@ -61,11 +61,11 @@ import java.util.logging.Logger;
public class HelloJsonServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
/* The port on which the server should run */
private int port = 50051;
private Server server;
private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()

View File

@ -86,11 +86,10 @@ public class DetailErrorSample {
new DetailErrorSample().run();
}
private Server server;
private ManagedChannel channel;
void run() throws Exception {
server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() {
Server server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
Metadata trailers = new Metadata();

View File

@ -67,11 +67,11 @@ public class ErrorHandlingClient {
new ErrorHandlingClient().run();
}
private Server server;
private ManagedChannel channel;
void run() throws Exception {
server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() {
// Port 0 means that the operating system will pick an available port to use.
Server server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
responseObserver.onError(Status.INTERNAL

View File

@ -31,6 +31,8 @@
package io.grpc.examples.header;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
@ -49,7 +51,8 @@ public class HeaderClientInterceptor implements ClientInterceptor {
private static final Logger logger = Logger.getLogger(HeaderClientInterceptor.class.getName());
private static Metadata.Key<String> customHeadKey =
@VisibleForTesting
static final Metadata.Key<String> CUSTOM_HEADER_KEY =
Metadata.Key.of("custom_client_header_key", Metadata.ASCII_STRING_MARSHALLER);
@Override
@ -60,13 +63,13 @@ public class HeaderClientInterceptor implements ClientInterceptor {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
/* put custom header */
headers.put(customHeadKey, "customRequestValue");
headers.put(CUSTOM_HEADER_KEY, "customRequestValue");
super.start(new SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
public void onHeaders(Metadata headers) {
/**
* if you don't need receive header from server,
* you can use {@link io.grpc.stub.MetadataUtils attachHeaders}
* you can use {@link io.grpc.stub.MetadataUtils#attachHeaders}
* directly to send header
*/
logger.info("header received from server:" + headers);

View File

@ -31,6 +31,8 @@
package io.grpc.examples.header;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
import io.grpc.Metadata;
import io.grpc.ServerCall;
@ -46,7 +48,8 @@ public class HeaderServerInterceptor implements ServerInterceptor {
private static final Logger logger = Logger.getLogger(HeaderServerInterceptor.class.getName());
private static Metadata.Key<String> customHeadKey =
@VisibleForTesting
static final Metadata.Key<String> CUSTOM_HEADER_KEY =
Metadata.Key.of("custom_server_header_key", Metadata.ASCII_STRING_MARSHALLER);
@ -59,7 +62,7 @@ public class HeaderServerInterceptor implements ServerInterceptor {
return next.startCall(new SimpleForwardingServerCall<ReqT, RespT>(call) {
@Override
public void sendHeaders(Metadata responseHeaders) {
responseHeaders.put(customHeadKey, "customRespondValue");
responseHeaders.put(CUSTOM_HEADER_KEY, "customRespondValue");
super.sendHeaders(responseHeaders);
}
}, requestHeaders);

View File

@ -50,11 +50,15 @@ public class HelloWorldClient {
/** Construct client connecting to HelloWorld server at {@code host:port}. */
public HelloWorldClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port)
this(ManagedChannelBuilder.forAddress(host, port)
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext(true)
.build();
.usePlaintext(true));
}
/** Construct client for accessing RouteGuide server using the existing channel. */
HelloWorldClient(ManagedChannelBuilder<?> channelBuilder) {
channel = channelBuilder.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}

View File

@ -44,11 +44,11 @@ import java.util.logging.Logger;
public class HelloWorldServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
/* The port on which the server should run */
private int port = 50051;
private Server server;
private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
@ -89,7 +89,7 @@ public class HelloWorldServer {
server.blockUntilShutdown();
}
private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {

View File

@ -31,6 +31,9 @@
package io.grpc.examples.routeguide;
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.Message;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
@ -58,6 +61,9 @@ public class RouteGuideClient {
private final RouteGuideBlockingStub blockingStub;
private final RouteGuideStub asyncStub;
private Random random = new Random();
private TestHelper testHelper;
/** Construct client for accessing RouteGuide server at {@code host:port}. */
public RouteGuideClient(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port).usePlaintext(true));
@ -85,8 +91,14 @@ public class RouteGuideClient {
Feature feature;
try {
feature = blockingStub.getFeature(request);
if (testHelper != null) {
testHelper.onMessage(feature);
}
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
warning("RPC failed: {0}", e.getStatus());
if (testHelper != null) {
testHelper.onRpcError(e);
}
return;
}
if (RouteGuideUtil.exists(feature)) {
@ -116,17 +128,19 @@ public class RouteGuideClient {
Iterator<Feature> features;
try {
features = blockingStub.listFeatures(request);
for (int i = 1; features.hasNext(); i++) {
Feature feature = features.next();
info("Result #" + i + ": {0}", feature);
if (testHelper != null) {
testHelper.onMessage(feature);
}
}
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
warning("RPC failed: {0}", e.getStatus());
if (testHelper != null) {
testHelper.onRpcError(e);
}
}
StringBuilder responseLog = new StringBuilder("Result: ");
while (features.hasNext()) {
Feature feature = features.next();
responseLog.append(feature);
}
info(responseLog.toString());
}
/**
@ -143,12 +157,17 @@ public class RouteGuideClient {
info("Finished trip with {0} points. Passed {1} features. "
+ "Travelled {2} meters. It took {3} seconds.", summary.getPointCount(),
summary.getFeatureCount(), summary.getDistance(), summary.getElapsedTime());
if (testHelper != null) {
testHelper.onMessage(summary);
}
}
@Override
public void onError(Throwable t) {
Status status = Status.fromThrowable(t);
logger.log(Level.WARNING, "RecordRoute Failed: {0}", status);
warning("RecordRoute Failed: {0}", Status.fromThrowable(t));
if (testHelper != null) {
testHelper.onRpcError(t);
}
finishLatch.countDown();
}
@ -162,15 +181,14 @@ public class RouteGuideClient {
StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver);
try {
// Send numPoints points randomly selected from the features list.
Random rand = new Random();
for (int i = 0; i < numPoints; ++i) {
int index = rand.nextInt(features.size());
int index = random.nextInt(features.size());
Point point = features.get(index).getLocation();
info("Visiting point {0}, {1}", RouteGuideUtil.getLatitude(point),
RouteGuideUtil.getLongitude(point));
requestObserver.onNext(point);
// Sleep for a bit before sending the next one.
Thread.sleep(rand.nextInt(1000) + 500);
Thread.sleep(random.nextInt(1000) + 500);
if (finishLatch.getCount() == 0) {
// RPC completed or errored before we finished sending.
// Sending further requests won't error, but they will just be thrown away.
@ -186,15 +204,17 @@ public class RouteGuideClient {
requestObserver.onCompleted();
// Receiving happens asynchronously
finishLatch.await(1, TimeUnit.MINUTES);
if (!finishLatch.await(1, TimeUnit.MINUTES)) {
warning("recordRoute can not finish within 1 minutes");
}
}
/**
* Bi-directional example, which can only be asynchronous. Send some chat messages, and print any
* chat messages that are sent from the server.
*/
public void routeChat() throws InterruptedException {
info("*** RoutChat");
public CountDownLatch routeChat() {
info("*** RouteChat");
final CountDownLatch finishLatch = new CountDownLatch(1);
StreamObserver<RouteNote> requestObserver =
asyncStub.routeChat(new StreamObserver<RouteNote>() {
@ -202,12 +222,17 @@ public class RouteGuideClient {
public void onNext(RouteNote note) {
info("Got message \"{0}\" at {1}, {2}", note.getMessage(), note.getLocation()
.getLatitude(), note.getLocation().getLongitude());
if (testHelper != null) {
testHelper.onMessage(note);
}
}
@Override
public void onError(Throwable t) {
Status status = Status.fromThrowable(t);
logger.log(Level.WARNING, "RouteChat Failed: {0}", status);
warning("RouteChat Failed: {0}", Status.fromThrowable(t));
if (testHelper != null) {
testHelper.onRpcError(t);
}
finishLatch.countDown();
}
@ -236,8 +261,8 @@ public class RouteGuideClient {
// Mark the end of requests
requestObserver.onCompleted();
// Receiving happens asynchronously
finishLatch.await(1, TimeUnit.MINUTES);
// return the latch while receiving happens asynchronously
return finishLatch;
}
/** Issues several different requests and then exits. */
@ -265,18 +290,55 @@ public class RouteGuideClient {
client.recordRoute(features, 10);
// Send and receive some notes.
client.routeChat();
CountDownLatch finishLatch = client.routeChat();
if (!finishLatch.await(1, TimeUnit.MINUTES)) {
client.warning("routeChat can not finish within 1 minutes");
}
} finally {
client.shutdown();
}
}
private static void info(String msg, Object... params) {
private void info(String msg, Object... params) {
logger.log(Level.INFO, msg, params);
}
private void warning(String msg, Object... params) {
logger.log(Level.WARNING, msg, params);
}
private RouteNote newNote(String message, int lat, int lon) {
return RouteNote.newBuilder().setMessage(message)
.setLocation(Point.newBuilder().setLatitude(lat).setLongitude(lon).build()).build();
}
/**
* Only used for unit test, as we do not want to introduce randomness in unit test.
*/
@VisibleForTesting
void setRandom(Random random) {
this.random = random;
}
/**
* Only used for helping unit test.
*/
@VisibleForTesting
interface TestHelper {
/**
* Used for verify/inspect message received from server.
*/
void onMessage(Message message);
/**
* Used for verify/inspect error received from server.
*/
void onRpcError(Throwable exception);
}
@VisibleForTesting
void setTestHelper(TestHelper testHelper) {
this.testHelper = testHelper;
}
}

View File

@ -188,7 +188,7 @@ public class RouteGuideServer {
int featureCount;
int distance;
Point previous;
long startTime = System.nanoTime();
final long startTime = System.nanoTime();
@Override
public void onNext(Point point) {
@ -295,7 +295,7 @@ public class RouteGuideServer {
double lat2 = RouteGuideUtil.getLatitude(end);
double lon1 = RouteGuideUtil.getLongitude(start);
double lon2 = RouteGuideUtil.getLongitude(end);
int r = 6371000; // metres
int r = 6371000; // meters
double phi1 = toRadians(lat1);
double phi2 = toRadians(lat2);
double deltaPhi = toRadians(lat2 - lat1);

View File

@ -0,0 +1,127 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.examples.header;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.Server;
import io.grpc.ServerCall;
import io.grpc.ServerCall.Listener;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.ServerInterceptors;
import io.grpc.StatusRuntimeException;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub;
import io.grpc.examples.helloworld.GreeterGrpc.GreeterImplBase;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
/**
* Unit tests for {@link HeaderClientInterceptor}.
* For demonstrating how to write gRPC unit test only.
* Not intended to provide a high code coverage or to test every major usecase.
*
* <p>For basic unit test examples see {@link io.grpc.examples.helloworld.HelloWorldClientTest} and
* {@link io.grpc.examples.helloworld.HelloWorldServerTest}.
*/
@RunWith(JUnit4.class)
public class HeaderClientInterceptorTest {
private final ServerInterceptor mockServerInterceptor = spy(
new ServerInterceptor() {
@Override
public <ReqT, RespT> Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
return next.startCall(call, headers);
}
});
private Server fakeServer;
private ManagedChannel inProcessChannel;
@Before
public void setUp() throws Exception {
String uniqueServerName = "fake server for " + getClass();
fakeServer = InProcessServerBuilder.forName(uniqueServerName)
.addService(ServerInterceptors.intercept(new GreeterImplBase() {}, mockServerInterceptor))
.directExecutor()
.build()
.start();
inProcessChannel = InProcessChannelBuilder.forName(uniqueServerName)
.intercept(new HeaderClientInterceptor())
.directExecutor()
.build();
}
@After
public void tearDown() {
inProcessChannel.shutdownNow();
fakeServer.shutdownNow();
}
@Test
public void clientHeaderDeliveredToServer() {
GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(inProcessChannel);
ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
try {
blockingStub.sayHello(HelloRequest.getDefaultInstance());
fail();
} catch (StatusRuntimeException expected) {
// expected because the method is not implemented at server side
}
verify(mockServerInterceptor).interceptCall(
Matchers.<ServerCall<HelloRequest, HelloReply>>any(),
metadataCaptor.capture(),
Matchers.<ServerCallHandler<HelloRequest, HelloReply>>any());
assertEquals(
"customRequestValue",
metadataCaptor.getValue().get(HeaderClientInterceptor.CUSTOM_HEADER_KEY));
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.examples.header;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Server;
import io.grpc.ServerInterceptors;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub;
import io.grpc.examples.helloworld.GreeterGrpc.GreeterImplBase;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.stub.StreamObserver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
/**
* Unit tests for {@link HeaderClientInterceptor}.
* For demonstrating how to write gRPC unit test only.
* Not intended to provide a high code coverage or to test every major usecase.
*
* <p>For basic unit test examples see {@link io.grpc.examples.helloworld.HelloWorldClientTest} and
* {@link io.grpc.examples.helloworld.HelloWorldServerTest}.
*/
@RunWith(JUnit4.class)
public class HeaderServerInterceptorTest {
private Server fakeServer;
private ManagedChannel inProcessChannel;
@Before
public void setUp() throws Exception {
String uniqueServerName = "fake server for " + getClass();
GreeterImplBase greeterImplBase =
new GreeterImplBase() {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
responseObserver.onNext(HelloReply.getDefaultInstance());
responseObserver.onCompleted();
}
};
fakeServer = InProcessServerBuilder.forName(uniqueServerName)
.addService(ServerInterceptors.intercept(greeterImplBase, new HeaderServerInterceptor()))
.directExecutor()
.build()
.start();
inProcessChannel = InProcessChannelBuilder.forName(uniqueServerName).directExecutor().build();
}
@After
public void tearDown() {
inProcessChannel.shutdownNow();
fakeServer.shutdownNow();
}
@Test
public void serverHeaderDeliveredToClient() {
class SpyingClientInterceptor implements ClientInterceptor {
ClientCall.Listener<?> spyListener;
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
spyListener = responseListener = spy(responseListener);
super.start(responseListener, headers);
}
};
}
}
SpyingClientInterceptor clientInterceptor = new SpyingClientInterceptor();
GreeterBlockingStub blockingStub =
GreeterGrpc.newBlockingStub(inProcessChannel).withInterceptors(clientInterceptor);
ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
blockingStub.sayHello(HelloRequest.getDefaultInstance());
assertNotNull(clientInterceptor.spyListener);
verify(clientInterceptor.spyListener).onHeaders(metadataCaptor.capture());
assertEquals(
"customRespondValue",
metadataCaptor.getValue().get(HeaderServerInterceptor.CUSTOM_HEADER_KEY));
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2015, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.examples.helloworld;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Server;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.stub.StreamObserver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
/**
* Unit tests for {@link HelloWorldClient}.
* For demonstrating how to write gRPC unit test only.
* Not intended to provide a high code coverage or to test every major usecase.
*
* <p>For more unit test examples see {@link io.grpc.examples.routeguide.RouteGuideClientTest} and
* {@link io.grpc.examples.routeguide.RouteGuideServerTest}.
*/
@RunWith(JUnit4.class)
public class HelloWorldClientTest {
private final GreeterGrpc.GreeterImplBase serviceImpl = spy(new GreeterGrpc.GreeterImplBase() {});
private Server fakeServer;
private HelloWorldClient client;
/**
* Creates and starts a fake in-process server, and creates a client with an in-process channel.
*/
@Before
public void setUp() throws Exception {
String uniqueServerName = "fake server for " + getClass();
fakeServer = InProcessServerBuilder
.forName(uniqueServerName).directExecutor().addService(serviceImpl).build().start();
ManagedChannelBuilder channelBuilder =
InProcessChannelBuilder.forName(uniqueServerName).directExecutor();
client = new HelloWorldClient(channelBuilder);
}
/**
* Shuts down the client and server.
*/
@After
public void tearDown() throws Exception {
client.shutdown();
fakeServer.shutdownNow();
}
/**
* To test the client, call from the client against the fake server, and verify behaviors or state
* changes from the server side.
*/
@Test
public void greet_messageDeliveredToServer() {
ArgumentCaptor<HelloRequest> requestCaptor = ArgumentCaptor.forClass(HelloRequest.class);
String testName = "test name";
client.greet(testName);
verify(serviceImpl)
.sayHello(requestCaptor.capture(), Matchers.<StreamObserver<HelloReply>>any());
assertEquals(testName, requestCaptor.getValue().getName());
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.examples.helloworld;
import static org.junit.Assert.assertEquals;
import io.grpc.ManagedChannel;
import io.grpc.Server;
import io.grpc.examples.helloworld.HelloWorldServer.GreeterImpl;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for {@link HelloWorldServer}.
* For demonstrating how to write gRPC unit test only.
* Not intended to provide a high code coverage or to test every major usecase.
*
* <p>For more unit test examples see {@link io.grpc.examples.routeguide.RouteGuideClientTest} and
* {@link io.grpc.examples.routeguide.RouteGuideServerTest}.
*/
@RunWith(JUnit4.class)
public class HelloWorldServerTest {
private static final String UNIQUE_SERVER_NAME =
"in-process server for " + HelloWorldServerTest.class;
private final Server inProcessServer = InProcessServerBuilder
.forName(UNIQUE_SERVER_NAME).addService(new GreeterImpl()).directExecutor().build();
private final ManagedChannel inProcessChannel =
InProcessChannelBuilder.forName(UNIQUE_SERVER_NAME).directExecutor().build();
/**
* Creates and starts the server with the {@link InProcessServerBuilder},
* and creates an in-process channel with the {@link InProcessChannelBuilder}.
*/
@Before
public void setUp() throws Exception {
inProcessServer.start();
}
/**
* Shuts down the in-process channel and server.
*/
@After
public void tearDown() {
inProcessChannel.shutdownNow();
inProcessServer.shutdownNow();
}
/**
* To test the server, make calls with a real stub using the in-process channel, and verify
* behaviors or state changes from the client side.
*/
@Test
public void greeterImpl_replyMessage() throws Exception {
GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(inProcessChannel);
String testName = "test name";
HelloReply reply = blockingStub.sayHello(HelloRequest.newBuilder().setName(testName).build());
assertEquals("Hello " + testName, reply.getMessage());
}
}

View File

@ -0,0 +1,577 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.examples.routeguide;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import com.google.protobuf.Message;
import io.grpc.Server;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.examples.routeguide.RouteGuideClient.TestHelper;
import io.grpc.examples.routeguide.RouteGuideGrpc.RouteGuideImplBase;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.stub.StreamObserver;
import io.grpc.util.MutableHandlerRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Unit tests for {@link RouteGuideClient}.
* For demonstrating how to write gRPC unit test only.
* Not intended to provide a high code coverage or to test every major usecase.
*
* <p>For basic unit test examples see {@link io.grpc.examples.helloworld.HelloWorldClientTest} and
* {@link io.grpc.examples.helloworld.HelloWorldServerTest}.
*/
@RunWith(JUnit4.class)
public class RouteGuideClientTest {
private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();
private final TestHelper testHelper = mock(TestHelper.class);
private final Random noRandomness =
new Random() {
int index;
boolean isForSleep;
/**
* Returns a number deterministically. If the random number is for sleep time, then return
* -500 so that {@code Thread.sleep(random.nextInt(1000) + 500)} sleeps 0 ms. Otherwise, it
* is for list index, then return incrementally (and cyclically).
*/
@Override
public int nextInt(int bound) {
int retVal = isForSleep ? -500 : (index++ % bound);
isForSleep = ! isForSleep;
return retVal;
}
};
private Server fakeServer;
private RouteGuideClient client;
@Before
public void setUp() throws Exception {
String uniqueServerName = "fake server for " + getClass();
// use a mutable service registry for later registering the service impl for each test case.
fakeServer = InProcessServerBuilder.forName(uniqueServerName)
.fallbackHandlerRegistry(serviceRegistry).directExecutor().build().start();
client =
new RouteGuideClient(InProcessChannelBuilder.forName(uniqueServerName).directExecutor());
client.setTestHelper(testHelper);
}
@After
public void tearDown() throws Exception {
client.shutdown();
fakeServer.shutdownNow();
}
/**
* Example for testing blocking unary call.
*/
@Test
public void getFeature() {
Point requestPoint = Point.newBuilder().setLatitude(-1).setLongitude(-1).build();
Point responsePoint = Point.newBuilder().setLatitude(-123).setLongitude(-123).build();
final AtomicReference<Point> pointDelivered = new AtomicReference<Point>();
final Feature responseFeature =
Feature.newBuilder().setName("dummyFeature").setLocation(responsePoint).build();
// implement the fake service
RouteGuideImplBase getFeatureImpl =
new RouteGuideImplBase() {
@Override
public void getFeature(Point point, StreamObserver<Feature> responseObserver) {
pointDelivered.set(point);
responseObserver.onNext(responseFeature);
responseObserver.onCompleted();
}
};
serviceRegistry.addService(getFeatureImpl);
client.getFeature(-1, -1);
assertEquals(requestPoint, pointDelivered.get());
verify(testHelper).onMessage(responseFeature);
verify(testHelper, never()).onRpcError(any(Throwable.class));
}
/**
* Example for testing blocking unary call.
*/
@Test
public void getFeature_error() {
Point requestPoint = Point.newBuilder().setLatitude(-1).setLongitude(-1).build();
final AtomicReference<Point> pointDelivered = new AtomicReference<Point>();
final StatusRuntimeException fakeError = new StatusRuntimeException(Status.DATA_LOSS);
// implement the fake service
RouteGuideImplBase getFeatureImpl =
new RouteGuideImplBase() {
@Override
public void getFeature(Point point, StreamObserver<Feature> responseObserver) {
pointDelivered.set(point);
responseObserver.onError(fakeError);
}
};
serviceRegistry.addService(getFeatureImpl);
client.getFeature(-1, -1);
assertEquals(requestPoint, pointDelivered.get());
ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
verify(testHelper).onRpcError(errorCaptor.capture());
assertEquals(fakeError.getStatus(), Status.fromThrowable(errorCaptor.getValue()));
}
/**
* Example for testing blocking server-streaming.
*/
@Test
public void listFeatures() {
final Feature responseFeature1 = Feature.newBuilder().setName("feature 1").build();
final Feature responseFeature2 = Feature.newBuilder().setName("feature 2").build();
final AtomicReference<Rectangle> rectangleDelivered = new AtomicReference<Rectangle>();
// implement the fake service
RouteGuideImplBase listFeaturesImpl =
new RouteGuideImplBase() {
@Override
public void listFeatures(Rectangle rectangle, StreamObserver<Feature> responseObserver) {
rectangleDelivered.set(rectangle);
// send two response messages
responseObserver.onNext(responseFeature1);
responseObserver.onNext(responseFeature2);
// complete the response
responseObserver.onCompleted();
}
};
serviceRegistry.addService(listFeaturesImpl);
client.listFeatures(1, 2, 3, 4);
assertEquals(Rectangle.newBuilder()
.setLo(Point.newBuilder().setLatitude(1).setLongitude(2).build())
.setHi(Point.newBuilder().setLatitude(3).setLongitude(4).build())
.build(),
rectangleDelivered.get());
verify(testHelper).onMessage(responseFeature1);
verify(testHelper).onMessage(responseFeature2);
verify(testHelper, never()).onRpcError(any(Throwable.class));
}
/**
* Example for testing blocking server-streaming.
*/
@Test
public void listFeatures_error() {
final Feature responseFeature1 =
Feature.newBuilder().setName("feature 1").build();
final AtomicReference<Rectangle> rectangleDelivered = new AtomicReference<Rectangle>();
final StatusRuntimeException fakeError = new StatusRuntimeException(Status.INVALID_ARGUMENT);
// implement the fake service
RouteGuideImplBase listFeaturesImpl =
new RouteGuideImplBase() {
@Override
public void listFeatures(Rectangle rectangle, StreamObserver<Feature> responseObserver) {
rectangleDelivered.set(rectangle);
// send one response message
responseObserver.onNext(responseFeature1);
// let the rpc fail
responseObserver.onError(fakeError);
}
};
serviceRegistry.addService(listFeaturesImpl);
client.listFeatures(1, 2, 3, 4);
assertEquals(Rectangle.newBuilder()
.setLo(Point.newBuilder().setLatitude(1).setLongitude(2).build())
.setHi(Point.newBuilder().setLatitude(3).setLongitude(4).build())
.build(),
rectangleDelivered.get());
ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
verify(testHelper).onMessage(responseFeature1);
verify(testHelper).onRpcError(errorCaptor.capture());
assertEquals(fakeError.getStatus(), Status.fromThrowable(errorCaptor.getValue()));
}
/**
* Example for testing async client-streaming.
*/
@Test
public void recordRoute() throws Exception {
client.setRandom(noRandomness);
Point point1 = Point.newBuilder().setLatitude(1).setLongitude(1).build();
Point point2 = Point.newBuilder().setLatitude(2).setLongitude(2).build();
Point point3 = Point.newBuilder().setLatitude(3).setLongitude(3).build();
Feature requestFeature1 =
Feature.newBuilder().setLocation(point1).build();
Feature requestFeature2 =
Feature.newBuilder().setLocation(point2).build();
Feature requestFeature3 =
Feature.newBuilder().setLocation(point3).build();
final List<Feature> features = Arrays.asList(
requestFeature1, requestFeature2, requestFeature3);
final List<Point> pointsDelivered = new ArrayList<Point>();
final RouteSummary fakeResponse = RouteSummary
.newBuilder()
.setPointCount(7)
.setFeatureCount(8)
.setDistance(9)
.setElapsedTime(10)
.build();
// implement the fake service
RouteGuideImplBase recordRouteImpl =
new RouteGuideImplBase() {
@Override
public StreamObserver<Point> recordRoute(
final StreamObserver<RouteSummary> responseObserver) {
StreamObserver<Point> requestObserver = new StreamObserver<Point>() {
@Override
public void onNext(Point value) {
pointsDelivered.add(value);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
responseObserver.onNext(fakeResponse);
responseObserver.onCompleted();
}
};
return requestObserver;
}
};
serviceRegistry.addService(recordRouteImpl);
// send requestFeature1, requestFeature2, requestFeature3, and then requestFeature1 again
client.recordRoute(features, 4);
assertEquals(
Arrays.asList(
requestFeature1.getLocation(),
requestFeature2.getLocation(),
requestFeature3.getLocation(),
requestFeature1.getLocation()),
pointsDelivered);
verify(testHelper).onMessage(fakeResponse);
verify(testHelper, never()).onRpcError(any(Throwable.class));
}
/**
* Example for testing async client-streaming.
*/
@Test
public void recordRoute_wrongResponse() throws Exception {
client.setRandom(noRandomness);
Point point1 = Point.newBuilder().setLatitude(1).setLongitude(1).build();
final Feature requestFeature1 =
Feature.newBuilder().setLocation(point1).build();
final List<Feature> features = Arrays.asList(requestFeature1);
// implement the fake service
RouteGuideImplBase recordRouteImpl =
new RouteGuideImplBase() {
@Override
public StreamObserver<Point> recordRoute(StreamObserver<RouteSummary> responseObserver) {
RouteSummary response = RouteSummary.getDefaultInstance();
// sending more than one responses is not right for client-streaming call.
responseObserver.onNext(response);
responseObserver.onNext(response);
responseObserver.onCompleted();
return new StreamObserver<Point>() {
@Override
public void onNext(Point value) {
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
}
};
}
};
serviceRegistry.addService(recordRouteImpl);
client.recordRoute(features, 4);
ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
verify(testHelper).onRpcError(errorCaptor.capture());
assertEquals(Status.Code.CANCELLED, Status.fromThrowable(errorCaptor.getValue()).getCode());
}
/**
* Example for testing async client-streaming.
*/
@Test
public void recordRoute_serverError() throws Exception {
client.setRandom(noRandomness);
Point point1 = Point.newBuilder().setLatitude(1).setLongitude(1).build();
final Feature requestFeature1 =
Feature.newBuilder().setLocation(point1).build();
final List<Feature> features = Arrays.asList(requestFeature1);
final StatusRuntimeException fakeError = new StatusRuntimeException(Status.INVALID_ARGUMENT);
// implement the fake service
RouteGuideImplBase recordRouteImpl =
new RouteGuideImplBase() {
@Override
public StreamObserver<Point> recordRoute(StreamObserver<RouteSummary> responseObserver) {
// send an error immediately
responseObserver.onError(fakeError);
StreamObserver<Point> requestObserver = new StreamObserver<Point>() {
@Override
public void onNext(Point value) {
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
}
};
return requestObserver;
}
};
serviceRegistry.addService(recordRouteImpl);
client.recordRoute(features, 4);
ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
verify(testHelper).onRpcError(errorCaptor.capture());
assertEquals(fakeError.getStatus(), Status.fromThrowable(errorCaptor.getValue()));
}
/**
* Example for testing bi-directional call.
*/
@Test
public void routeChat_simpleResponse() throws Exception {
RouteNote fakeResponse1 = RouteNote.newBuilder().setMessage("dummy msg1").build();
RouteNote fakeResponse2 = RouteNote.newBuilder().setMessage("dummy msg2").build();
final List<String> messagesDelivered = new ArrayList<String>();
final List<Point> locationsDelivered = new ArrayList<Point>();
final AtomicReference<StreamObserver<RouteNote>> responseObserverRef =
new AtomicReference<StreamObserver<RouteNote>>();
final CountDownLatch allRequestsDelivered = new CountDownLatch(1);
// implement the fake service
RouteGuideImplBase routeChatImpl =
new RouteGuideImplBase() {
@Override
public StreamObserver<RouteNote> routeChat(StreamObserver<RouteNote> responseObserver) {
responseObserverRef.set(responseObserver);
StreamObserver<RouteNote> requestObserver = new StreamObserver<RouteNote>() {
@Override
public void onNext(RouteNote value) {
messagesDelivered.add(value.getMessage());
locationsDelivered.add(value.getLocation());
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
allRequestsDelivered.countDown();
}
};
return requestObserver;
}
};
serviceRegistry.addService(routeChatImpl);
// start routeChat
CountDownLatch latch = client.routeChat();
// request message sent and delivered for four times
assertTrue(allRequestsDelivered.await(1, TimeUnit.SECONDS));
assertEquals(
Arrays.asList("First message", "Second message", "Third message", "Fourth message"),
messagesDelivered);
assertEquals(
Arrays.asList(
Point.newBuilder().setLatitude(0).setLongitude(0).build(),
Point.newBuilder().setLatitude(0).setLongitude(1).build(),
Point.newBuilder().setLatitude(1).setLongitude(0).build(),
Point.newBuilder().setLatitude(1).setLongitude(1).build()
),
locationsDelivered);
// Let the server send out two simple response messages
// and verify that the client receives them.
// Allow some timeout for verify() if not using directExecutor
responseObserverRef.get().onNext(fakeResponse1);
verify(testHelper).onMessage(fakeResponse1);
responseObserverRef.get().onNext(fakeResponse2);
verify(testHelper).onMessage(fakeResponse2);
// let server complete.
responseObserverRef.get().onCompleted();
assertTrue(latch.await(1, TimeUnit.SECONDS));
verify(testHelper, never()).onRpcError(any(Throwable.class));
}
/**
* Example for testing bi-directional call.
*/
@Test
public void routeChat_echoResponse() throws Exception {
final List<RouteNote> notesDelivered = new ArrayList<RouteNote>();
// implement the fake service
RouteGuideImplBase routeChatImpl =
new RouteGuideImplBase() {
@Override
public StreamObserver<RouteNote> routeChat(
final StreamObserver<RouteNote> responseObserver) {
StreamObserver<RouteNote> requestObserver = new StreamObserver<RouteNote>() {
@Override
public void onNext(RouteNote value) {
notesDelivered.add(value);
responseObserver.onNext(value);
}
@Override
public void onError(Throwable t) {
responseObserver.onError(t);
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
return requestObserver;
}
};
serviceRegistry.addService(routeChatImpl);
client.routeChat().await(1, TimeUnit.SECONDS);
String[] messages =
{"First message", "Second message", "Third message", "Fourth message"};
for (int i = 0; i < 4; i++) {
verify(testHelper).onMessage(notesDelivered.get(i));
assertEquals(messages[i], notesDelivered.get(i).getMessage());
}
verify(testHelper, never()).onRpcError(any(Throwable.class));
}
/**
* Example for testing bi-directional call.
*/
@Test
public void routeChat_errorResponse() throws Exception {
final List<RouteNote> notesDelivered = new ArrayList<RouteNote>();
final StatusRuntimeException fakeError = new StatusRuntimeException(Status.PERMISSION_DENIED);
// implement the fake service
RouteGuideImplBase routeChatImpl =
new RouteGuideImplBase() {
@Override
public StreamObserver<RouteNote> routeChat(
final StreamObserver<RouteNote> responseObserver) {
StreamObserver<RouteNote> requestObserver = new StreamObserver<RouteNote>() {
@Override
public void onNext(RouteNote value) {
notesDelivered.add(value);
responseObserver.onError(fakeError);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
return requestObserver;
}
};
serviceRegistry.addService(routeChatImpl);
client.routeChat().await(1, TimeUnit.SECONDS);
assertEquals("First message", notesDelivered.get(0).getMessage());
verify(testHelper, never()).onMessage(any(Message.class));
ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
verify(testHelper).onRpcError(errorCaptor.capture());
assertEquals(fakeError.getStatus(), Status.fromThrowable(errorCaptor.getValue()));
}
}

View File

@ -0,0 +1,282 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.examples.routeguide;
import static junit.framework.TestCase.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import io.grpc.ManagedChannel;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.stub.StreamObserver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Unit tests for {@link RouteGuideServer}.
* For demonstrating how to write gRPC unit test only.
* Not intended to provide a high code coverage or to test every major usecase.
*
* <p>For basic unit test examples see {@link io.grpc.examples.helloworld.HelloWorldClientTest} and
* {@link io.grpc.examples.helloworld.HelloWorldServerTest}.
*/
@RunWith(JUnit4.class)
public class RouteGuideServerTest {
private RouteGuideServer server;
private ManagedChannel inProcessChannel;
private Collection<Feature> features;
@Before
public void setUp() throws Exception {
String uniqueServerName = "in-process server for " + getClass();
features = new ArrayList<Feature>();
// use directExecutor for both InProcessServerBuilder and InProcessChannelBuilder can reduce the
// usage timeouts and latches in test. But we still add timeout and latches where they would be
// needed if no directExecutor were used, just for demo purpose.
server = new RouteGuideServer(
InProcessServerBuilder.forName(uniqueServerName).directExecutor(), 0, features);
server.start();
inProcessChannel = InProcessChannelBuilder.forName(uniqueServerName).directExecutor().build();
}
@After
public void tearDown() throws Exception {
inProcessChannel.shutdownNow();
server.stop();
}
@Test
public void getFeature() {
Point point = Point.newBuilder().setLongitude(1).setLatitude(1).build();
Feature unnamedFeature = Feature.newBuilder()
.setName("").setLocation(point).build();
RouteGuideGrpc.RouteGuideBlockingStub stub = RouteGuideGrpc.newBlockingStub(inProcessChannel);
// feature not found in the server
Feature feature = stub.getFeature(point);
assertEquals(unnamedFeature, feature);
// feature found in the server
Feature namedFeature = Feature.newBuilder()
.setName("name").setLocation(point).build();
features.add(namedFeature);
feature = stub.getFeature(point);
assertEquals(namedFeature, feature);
}
@Test
public void listFeatures() throws Exception {
// setup
Rectangle rect = Rectangle.newBuilder()
.setLo(Point.newBuilder().setLongitude(0).setLatitude(0).build())
.setHi(Point.newBuilder().setLongitude(10).setLatitude(10).build())
.build();
Feature f1 = Feature.newBuilder()
.setLocation(Point.newBuilder().setLongitude(-1).setLatitude(-1).build())
.setName("f1")
.build(); // not inside rect
Feature f2 = Feature.newBuilder()
.setLocation(Point.newBuilder().setLongitude(2).setLatitude(2).build())
.setName("f2")
.build();
Feature f3 = Feature.newBuilder()
.setLocation(Point.newBuilder().setLongitude(3).setLatitude(3).build())
.setName("f3")
.build();
Feature f4 = Feature.newBuilder()
.setLocation(Point.newBuilder().setLongitude(4).setLatitude(4).build())
.build(); // unamed
features.add(f1);
features.add(f2);
features.add(f3);
features.add(f4);
final Collection<Feature> result = new HashSet<Feature>();
final CountDownLatch latch = new CountDownLatch(1);
StreamObserver<Feature> responseObserver =
new StreamObserver<Feature>() {
@Override
public void onNext(Feature value) {
result.add(value);
}
@Override
public void onError(Throwable t) {
fail();
}
@Override
public void onCompleted() {
latch.countDown();
}
};
RouteGuideGrpc.RouteGuideStub stub = RouteGuideGrpc.newStub(inProcessChannel);
// run
stub.listFeatures(rect, responseObserver);
assertTrue(latch.await(1, TimeUnit.SECONDS));
// verify
assertEquals(new HashSet<Feature>(Arrays.asList(f2, f3)), result);
}
@Test
public void recordRoute() {
Point p1 = Point.newBuilder().setLongitude(1000).setLatitude(1000).build();
Point p2 = Point.newBuilder().setLongitude(2000).setLatitude(2000).build();
Point p3 = Point.newBuilder().setLongitude(3000).setLatitude(3000).build();
Point p4 = Point.newBuilder().setLongitude(4000).setLatitude(4000).build();
Feature f1 = Feature.newBuilder().setLocation(p1).build(); // unamed
Feature f2 = Feature.newBuilder().setLocation(p2).setName("f2").build();
Feature f3 = Feature.newBuilder().setLocation(p3).setName("f3").build();
Feature f4 = Feature.newBuilder().setLocation(p4).build(); // unamed
features.add(f1);
features.add(f2);
features.add(f3);
features.add(f4);
@SuppressWarnings("unchecked")
StreamObserver<RouteSummary> responseObserver =
(StreamObserver<RouteSummary>) mock(StreamObserver.class);
RouteGuideGrpc.RouteGuideStub stub = RouteGuideGrpc.newStub(inProcessChannel);
ArgumentCaptor<RouteSummary> routeSummaryCaptor = ArgumentCaptor.forClass(RouteSummary.class);
StreamObserver<Point> requestObserver = stub.recordRoute(responseObserver);
requestObserver.onNext(p1);
requestObserver.onNext(p2);
requestObserver.onNext(p3);
requestObserver.onNext(p4);
verify(responseObserver, never()).onNext(any(RouteSummary.class));
requestObserver.onCompleted();
// allow some ms to let client receive the response. Similar usage later on.
verify(responseObserver, timeout(100)).onNext(routeSummaryCaptor.capture());
RouteSummary summary = routeSummaryCaptor.getValue();
assertEquals(45, summary.getDistance()); // 45 is the hard coded distance from p1 to p4.
assertEquals(2, summary.getFeatureCount());
verify(responseObserver, timeout(100)).onCompleted();
verify(responseObserver, never()).onError(any(Throwable.class));
}
@Test
public void routeChat() {
Point p1 = Point.newBuilder().setLongitude(1).setLatitude(1).build();
Point p2 = Point.newBuilder().setLongitude(2).setLatitude(2).build();
RouteNote n1 = RouteNote.newBuilder().setLocation(p1).setMessage("m1").build();
RouteNote n2 = RouteNote.newBuilder().setLocation(p2).setMessage("m2").build();
RouteNote n3 = RouteNote.newBuilder().setLocation(p1).setMessage("m3").build();
RouteNote n4 = RouteNote.newBuilder().setLocation(p2).setMessage("m4").build();
RouteNote n5 = RouteNote.newBuilder().setLocation(p1).setMessage("m5").build();
RouteNote n6 = RouteNote.newBuilder().setLocation(p1).setMessage("m6").build();
int timesOnNext = 0;
@SuppressWarnings("unchecked")
StreamObserver<RouteNote> responseObserver =
(StreamObserver<RouteNote>) mock(StreamObserver.class);
RouteGuideGrpc.RouteGuideStub stub = RouteGuideGrpc.newStub(inProcessChannel);
StreamObserver<RouteNote> requestObserver = stub.routeChat(responseObserver);
verify(responseObserver, never()).onNext(any(RouteNote.class));
requestObserver.onNext(n1);
verify(responseObserver, never()).onNext(any(RouteNote.class));
requestObserver.onNext(n2);
verify(responseObserver, never()).onNext(any(RouteNote.class));
requestObserver.onNext(n3);
ArgumentCaptor<RouteNote> routeNoteCaptor = ArgumentCaptor.forClass(RouteNote.class);
verify(responseObserver, timeout(100).times(++timesOnNext)).onNext(routeNoteCaptor.capture());
RouteNote result = routeNoteCaptor.getValue();
assertEquals(p1, result.getLocation());
assertEquals("m1", result.getMessage());
requestObserver.onNext(n4);
routeNoteCaptor = ArgumentCaptor.forClass(RouteNote.class);
verify(responseObserver, timeout(100).times(++timesOnNext)).onNext(routeNoteCaptor.capture());
result = routeNoteCaptor.getAllValues().get(timesOnNext - 1);
assertEquals(p2, result.getLocation());
assertEquals("m2", result.getMessage());
requestObserver.onNext(n5);
routeNoteCaptor = ArgumentCaptor.forClass(RouteNote.class);
timesOnNext += 2;
verify(responseObserver, timeout(100).times(timesOnNext)).onNext(routeNoteCaptor.capture());
result = routeNoteCaptor.getAllValues().get(timesOnNext - 2);
assertEquals(p1, result.getLocation());
assertEquals("m1", result.getMessage());
result = routeNoteCaptor.getAllValues().get(timesOnNext - 1);
assertEquals(p1, result.getLocation());
assertEquals("m3", result.getMessage());
requestObserver.onNext(n6);
routeNoteCaptor = ArgumentCaptor.forClass(RouteNote.class);
timesOnNext += 3;
verify(responseObserver, timeout(100).times(timesOnNext)).onNext(routeNoteCaptor.capture());
result = routeNoteCaptor.getAllValues().get(timesOnNext - 3);
assertEquals(p1, result.getLocation());
assertEquals("m1", result.getMessage());
result = routeNoteCaptor.getAllValues().get(timesOnNext - 2);
assertEquals(p1, result.getLocation());
assertEquals("m3", result.getMessage());
result = routeNoteCaptor.getAllValues().get(timesOnNext - 1);
assertEquals(p1, result.getLocation());
assertEquals("m5", result.getMessage());
requestObserver.onCompleted();
verify(responseObserver, timeout(100)).onCompleted();
verify(responseObserver, never()).onError(any(Throwable.class));
}
}