diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index 64ea21800fd55fd3ceebea7a570838aac2e130b4..eb9cd7cf0cff5af2c793ce5e2f5586efdf3d80f6 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -46,47 +46,18 @@ namespace Grpc.Core.Tests
     public class ClientServerTest
     {
         const string Host = "127.0.0.1";
-        const string ServiceName = "tests.Test";
-
-        static readonly Method<string, string> EchoMethod = new Method<string, string>(
-            MethodType.Unary,
-            ServiceName,
-            "Echo",
-            Marshallers.StringMarshaller,
-            Marshallers.StringMarshaller);
-
-        static readonly Method<string, string> ConcatAndEchoMethod = new Method<string, string>(
-            MethodType.ClientStreaming,
-            ServiceName,
-            "ConcatAndEcho",
-            Marshallers.StringMarshaller,
-            Marshallers.StringMarshaller);
-
-        static readonly Method<string, string> NonexistentMethod = new Method<string, string>(
-            MethodType.Unary,
-            ServiceName,
-            "NonexistentMethod",
-            Marshallers.StringMarshaller,
-            Marshallers.StringMarshaller);
-
-        static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
-            .AddMethod(EchoMethod, EchoHandler)
-            .AddMethod(ConcatAndEchoMethod, ConcatAndEchoHandler)
-            .Build();
 
+        MockServiceHelper helper;
         Server server;
         Channel channel;
 
         [SetUp]
         public void Init()
         {
-            server = new Server
-            {
-                Services = { ServiceDefinition },
-                Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
-            };
+            helper = new MockServiceHelper(Host);
+            server = helper.GetServer();
             server.Start();
-            channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure);
+            channel = helper.GetChannel();
         }
 
         [TearDown]
@@ -103,86 +74,79 @@ namespace Grpc.Core.Tests
         }
 
         [Test]
-        public void UnaryCall()
+        public async Task UnaryCall()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            Assert.AreEqual("ABC", Calls.BlockingUnaryCall(callDetails, "ABC"));
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return request;
+            });
+
+            Assert.AreEqual("ABC", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC"));
+
+            Assert.AreEqual("ABC", await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "ABC"));
         }
 
         [Test]
         public void UnaryCall_ServerHandlerThrows()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            try
+            helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
             {
-                Calls.BlockingUnaryCall(callDetails, "THROW");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); 
-            }
+                throw new Exception("This was thrown on purpose by a test");
+            });
+                
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unknown, ex.Status.StatusCode); 
+
+            var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unknown, ex2.Status.StatusCode);
         }
 
         [Test]
         public void UnaryCall_ServerHandlerThrowsRpcException()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            try
-            {
-                Calls.BlockingUnaryCall(callDetails, "THROW_UNAUTHENTICATED");
-                Assert.Fail();
-            }
-            catch (RpcException e)
+            helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
             {
-                Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode);
-            }
+                throw new RpcException(new Status(StatusCode.Unauthenticated, ""));
+            });
+
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+
+            var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
         }
 
         [Test]
         public void UnaryCall_ServerHandlerSetsStatus()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            try
-            {
-                Calls.BlockingUnaryCall(callDetails, "SET_UNAUTHENTICATED");
-                Assert.Fail();
-            }
-            catch (RpcException e)
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
             {
-                Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode); 
-            }
-        }
+                context.Status = new Status(StatusCode.Unauthenticated, "");
+                return "";
+            });
 
-        [Test]
-        public async Task AsyncUnaryCall()
-        {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            var result = await Calls.AsyncUnaryCall(callDetails, "ABC");
-            Assert.AreEqual("ABC", result);
-        }
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
 
-        [Test]
-        public async Task AsyncUnaryCall_ServerHandlerThrows()
-        {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            try
-            {
-                await Calls.AsyncUnaryCall(callDetails, "THROW");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
-            }
+            var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
         }
 
         [Test]
         public async Task ClientStreamingCall()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, ConcatAndEchoMethod, new CallOptions());
-            var call = Calls.AsyncClientStreamingCall(callDetails);
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                string result = "";
+                await requestStream.ForEach(async (request) =>
+                {
+                    result += request;
+                });
+                await Task.Delay(100);
+                return result;
+            });
 
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());
             await call.RequestStream.WriteAll(new string[] { "A", "B", "C" });
             Assert.AreEqual("ABC", await call.ResponseAsync);
         }
@@ -190,34 +154,46 @@ namespace Grpc.Core.Tests
         [Test]
         public async Task ClientStreamingCall_CancelAfterBegin()
         {
+            var barrier = new TaskCompletionSource<object>();
+
+            helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+            {
+                barrier.SetResult(null);
+                await requestStream.ToList();
+                return "";
+            });
+
             var cts = new CancellationTokenSource();
-            var callDetails = new CallInvocationDetails<string, string>(channel, ConcatAndEchoMethod, new CallOptions(cancellationToken: cts.Token));
-            var call = Calls.AsyncClientStreamingCall(callDetails);
+            var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
 
-            // TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it.
-            await Task.Delay(1000);
+            await barrier.Task;  // make sure the handler has started.
             cts.Cancel();
 
-            try
-            {
-                await call.ResponseAsync;
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode);
-            }
+            var ex = Assert.Throws<RpcException>(async () => await call.ResponseAsync);
+            Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
         }
 
         [Test]
         public void AsyncUnaryCall_EchoMetadata()
         {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                foreach (Metadata.Entry metadataEntry in context.RequestHeaders)
+                {
+                    if (metadataEntry.Key != "user-agent")
+                    {
+                        context.ResponseTrailers.Add(metadataEntry);
+                    }
+                }
+                return "";
+            });
+
             var headers = new Metadata
             {
                 new Metadata.Entry("ascii-header", "abcdefg"),
                 new Metadata.Entry("binary-header-bin", new byte[] { 1, 2, 3, 0, 0xff }),
             };
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions(headers: headers));
-            var call = Calls.AsyncUnaryCall(callDetails, "ABC");
+            var call = Calls.AsyncUnaryCall(helper.CreateUnaryCall(new CallOptions(headers: headers)), "ABC");
 
             Assert.AreEqual("ABC", call.ResponseAsync.Result);
 
@@ -236,15 +212,13 @@ namespace Grpc.Core.Tests
         public void UnaryCall_DisposedChannel()
         {
             channel.Dispose();
-
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(callDetails, "ABC"));
+            Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC"));
         }
 
         [Test]
         public void UnaryCallPerformance()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
+            var callDetails = helper.CreateUnaryCall();
             BenchmarkUtil.RunBenchmark(100, 100,
                                        () => { Calls.BlockingUnaryCall(callDetails, "ABC"); });
         }
@@ -252,44 +226,57 @@ namespace Grpc.Core.Tests
         [Test]
         public void UnknownMethodHandler()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, NonexistentMethod, new CallOptions());
-            try
-            {
-                Calls.BlockingUnaryCall(callDetails, "ABC");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode);
-            }
+            var nonexistentMethod = new Method<string, string>(
+                MethodType.Unary,
+                MockServiceHelper.ServiceName,
+                "NonExistentMethod",
+                Marshallers.StringMarshaller,
+                Marshallers.StringMarshaller);
+
+            var callDetails = new CallInvocationDetails<string, string>(channel, nonexistentMethod, new CallOptions());
+
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(callDetails, "abc"));
+            Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode);
         }
 
         [Test]
         public void UserAgentStringPresent()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            string userAgent = Calls.BlockingUnaryCall(callDetails, "RETURN-USER-AGENT");
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value;
+            });
+
+            string userAgent = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
             Assert.IsTrue(userAgent.StartsWith("grpc-csharp/"));
         }
 
         [Test]
         public void PeerInfoPresent()
         {
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            string peer = Calls.BlockingUnaryCall(callDetails, "RETURN-PEER");
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return context.Peer;
+            });
+
+            string peer = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
             Assert.IsTrue(peer.Contains(Host));
         }
 
         [Test]
         public async Task Channel_WaitForStateChangedAsync()
         {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                return request;
+            });
+
             Assert.Throws(typeof(TaskCanceledException), 
                 async () => await channel.WaitForStateChangedAsync(channel.State, DateTime.UtcNow.AddMilliseconds(10)));
 
             var stateChangedTask = channel.WaitForStateChangedAsync(channel.State);
 
-            var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
-            await Calls.AsyncUnaryCall(callDetails, "abc");
+            await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc");
 
             await stateChangedTask;
             Assert.AreEqual(ChannelState.Ready, channel.State);
@@ -300,62 +287,9 @@ namespace Grpc.Core.Tests
         {
             await channel.ConnectAsync();
             Assert.AreEqual(ChannelState.Ready, channel.State);
+
             await channel.ConnectAsync(DateTime.UtcNow.AddMilliseconds(1000));
             Assert.AreEqual(ChannelState.Ready, channel.State);
         }
-
-        private static async Task<string> EchoHandler(string request, ServerCallContext context)
-        {
-            foreach (Metadata.Entry metadataEntry in context.RequestHeaders)
-            {
-                if (metadataEntry.Key != "user-agent")
-                {
-                    context.ResponseTrailers.Add(metadataEntry);
-                }
-            }
-
-            if (request == "RETURN-USER-AGENT")
-            {
-                return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value;
-            }
-
-            if (request == "RETURN-PEER")
-            {
-                return context.Peer;
-            }
-
-            if (request == "THROW")
-            {
-                throw new Exception("This was thrown on purpose by a test");
-            }
-
-            if (request == "THROW_UNAUTHENTICATED")
-            {
-                throw new RpcException(new Status(StatusCode.Unauthenticated, ""));
-            }
-
-            if (request == "SET_UNAUTHENTICATED")
-            {
-                context.Status = new Status(StatusCode.Unauthenticated, "");
-            }
-
-            return request;
-        }
-
-        private static async Task<string> ConcatAndEchoHandler(IAsyncStreamReader<string> requestStream, ServerCallContext context)
-        {
-            string result = "";
-            await requestStream.ForEach(async (request) =>
-            {
-                if (request == "THROW")
-                {
-                    throw new Exception("This was thrown on purpose by a test");
-                }
-                result += request;
-            });
-            // simulate processing takes some time.
-            await Task.Delay(250);
-            return result;
-        }
     }
 }
diff --git a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
index 51709813bf89ed6b1537ecb3257e130c8d9167cd..a52020cf40250cdfb016322bca8eb08e202468f3 100644
--- a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
+++ b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
@@ -113,16 +113,9 @@ namespace Grpc.Core.Tests
                 return "FAIL";
             });
 
-            try
-            {
-                Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MinValue)), "abc");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
-                Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
-            }
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MinValue)), "abc"));
+            // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+            Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
         }
 
         [Test]
@@ -133,16 +126,9 @@ namespace Grpc.Core.Tests
                 return "FAIL";
             });
 
-            try
-            {
-                Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
-                Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
-            }
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc"));
+            // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+            Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
         }
 
         [Test]
@@ -163,16 +149,9 @@ namespace Grpc.Core.Tests
                 return "";
             });
 
-            try
-            {
-                Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc");
-                Assert.Fail();
-            }
-            catch (RpcException e)
-            {
-                // We can't guarantee the status code is always DeadlineExceeded. See issue #2685.
-                Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
-            }
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc"));
+            // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+            Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
 
             lock (myLock)
             {