diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec
index bda0352c26f466ac8c741022874456e2aaa5a010..18f62adfd559c2765123ad47ab0d9999d470481a 100755
--- a/src/ruby/grpc.gemspec
+++ b/src/ruby/grpc.gemspec
@@ -29,9 +29,8 @@ Gem::Specification.new do |s|
   s.platform      = Gem::Platform::RUBY
 
   s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1'
-  s.add_dependency 'googleauth', '~> 0.4'  # reqd for interop tests
+  s.add_dependency 'googleauth', '~> 0.4'
   s.add_dependency 'logging', '~> 2.0'
-  s.add_dependency 'minitest', '~> 5.4'  # reqd for interop tests
 
   s.add_development_dependency 'simplecov', '~> 0.9'
   s.add_development_dependency 'bundler', '~> 1.9'
diff --git a/src/ruby/pb/test/client.rb b/src/ruby/pb/test/client.rb
index 66c78f8bf8fe6e7a6aadee4e3843c674ebc62630..164e304b4d799ca4946db9fd840937a9495be466 100755
--- a/src/ruby/pb/test/client.rb
+++ b/src/ruby/pb/test/client.rb
@@ -46,8 +46,6 @@ $LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir)
 $LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
 
 require 'optparse'
-require 'minitest'
-require 'minitest/assertions'
 
 require 'grpc'
 require 'googleauth'
@@ -61,6 +59,15 @@ require 'signet/ssl_config'
 
 AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR
 
+# AssertionError is use to indicate interop test failures.
+class AssertionError < RuntimeError; end
+
+# Fails with AssertionError if the block does evaluate to true
+def assert(msg = 'unknown cause')
+  fail 'No assertion block provided' unless block_given?
+  fail AssertionError, msg unless yield
+end
+
 # loads the certificates used to access the test server securely.
 def load_test_certs
   this_dir = File.expand_path(File.dirname(__FILE__))
@@ -141,10 +148,8 @@ end
 
 # a PingPongPlayer implements the ping pong bidi test.
 class PingPongPlayer
-  include Minitest::Assertions
   include Grpc::Testing
   include Grpc::Testing::PayloadType
-  attr_accessor :assertions # required by Minitest::Assertions
   attr_accessor :queue
   attr_accessor :canceller_op
 
@@ -152,7 +157,6 @@ class PingPongPlayer
   def initialize(msg_sizes)
     @queue = Queue.new
     @msg_sizes = msg_sizes
-    @assertions = 0  # required by Minitest::Assertions
     @canceller_op = nil  # used to cancel after the first response
   end
 
@@ -167,9 +171,10 @@ class PingPongPlayer
                         response_parameters: [p_cls.new(size: resp_size)])
       yield req
       resp = @queue.pop
-      assert_equal(:COMPRESSABLE, resp.payload.type, 'payload type is wrong')
-      assert_equal(resp_size, resp.payload.body.length,
-                   "payload body #{count} has the wrong length")
+      assert('payload type is wrong') { :COMPRESSABLE == resp.payload.type }
+      assert("payload body #{count} has the wrong length") do
+        resp_size == resp.payload.body.length
+      end
       p "OK: ping_pong #{count}"
       count += 1
       unless @canceller_op.nil?
@@ -182,20 +187,17 @@ end
 
 # defines methods corresponding to each interop test case.
 class NamedTests
-  include Minitest::Assertions
   include Grpc::Testing
   include Grpc::Testing::PayloadType
-  attr_accessor :assertions # required by Minitest::Assertions
 
   def initialize(stub, args)
-    @assertions = 0  # required by Minitest::Assertions
     @stub = stub
     @args = args
   end
 
   def empty_unary
     resp = @stub.empty_call(Empty.new)
-    assert resp.is_a?(Empty), 'empty_unary: invalid response'
+    assert('empty_unary: invalid response') { resp.is_a?(Empty) }
     p 'OK: empty_unary'
   end
 
@@ -214,28 +216,28 @@ class NamedTests
     wanted_email = MultiJson.load(json_key)['client_email']
     resp = perform_large_unary(fill_username: true,
                                fill_oauth_scope: true)
-    assert_equal(wanted_email, resp.username,
-                 'service_account_creds: incorrect username')
-    assert(@args.oauth_scope.include?(resp.oauth_scope),
-           'service_account_creds: incorrect oauth_scope')
-    p 'OK: service_account_creds'
+    assert("#{__callee__}: bad username") { wanted_email == resp.username }
+    assert("#{__callee__}: bad oauth scope") do
+      @args.oauth_scope.include?(resp.oauth_scope)
+    end
+    p "OK: #{__callee__}"
   end
 
   def jwt_token_creds
     json_key = File.read(ENV[AUTH_ENV])
     wanted_email = MultiJson.load(json_key)['client_email']
     resp = perform_large_unary(fill_username: true)
-    assert_equal(wanted_email, resp.username,
-                 'service_account_creds: incorrect username')
-    p 'OK: jwt_token_creds'
+    assert("#{__callee__}: bad username") { wanted_email == resp.username }
+    p "OK: #{__callee__}"
   end
 
   def compute_engine_creds
     resp = perform_large_unary(fill_username: true,
                                fill_oauth_scope: true)
-    assert_equal(@args.default_service_account, resp.username,
-                 'compute_engine_creds: incorrect username')
-    p 'OK: compute_engine_creds'
+    assert("#{__callee__}: bad username") do
+      @args.default_service_account == resp.username
+    end
+    p "OK: #{__callee__}"
   end
 
   def oauth2_auth_token
@@ -243,10 +245,10 @@ class NamedTests
                                fill_oauth_scope: true)
     json_key = File.read(ENV[AUTH_ENV])
     wanted_email = MultiJson.load(json_key)['client_email']
-    assert_equal(wanted_email, resp.username,
-                 "#{__callee__}: incorrect username")
-    assert(@args.oauth_scope.include?(resp.oauth_scope),
-           "#{__callee__}: incorrect oauth_scope")
+    assert("#{__callee__}: bad username") { wanted_email == resp.username }
+    assert("#{__callee__}: bad oauth scope") do
+      @args.oauth_scope.include?(resp.oauth_scope)
+    end
     p "OK: #{__callee__}"
   end
 
@@ -258,10 +260,10 @@ class NamedTests
                                **kw)
     json_key = File.read(ENV[AUTH_ENV])
     wanted_email = MultiJson.load(json_key)['client_email']
-    assert_equal(wanted_email, resp.username,
-                 "#{__callee__}: incorrect username")
-    assert(@args.oauth_scope.include?(resp.oauth_scope),
-           "#{__callee__}: incorrect oauth_scope")
+    assert("#{__callee__}: bad username") { wanted_email == resp.username }
+    assert("#{__callee__}: bad oauth scope") do
+      @args.oauth_scope.include?(resp.oauth_scope)
+    end
     p "OK: #{__callee__}"
   end
 
@@ -273,9 +275,10 @@ class NamedTests
       StreamingInputCallRequest.new(payload: req)
     end
     resp = @stub.streaming_input_call(reqs)
-    assert_equal(wanted_aggregate_size, resp.aggregated_payload_size,
-                 'client_streaming: aggregate payload size is incorrect')
-    p 'OK: client_streaming'
+    assert("#{__callee__}: aggregate payload size is incorrect") do
+      wanted_aggregate_size == resp.aggregated_payload_size
+    end
+    p "OK: #{__callee__}"
   end
 
   def server_streaming
@@ -285,13 +288,15 @@ class NamedTests
                                          response_parameters: response_spec)
     resps = @stub.streaming_output_call(req)
     resps.each_with_index do |r, i|
-      assert i < msg_sizes.length, 'too many responses'
-      assert_equal(:COMPRESSABLE, r.payload.type,
-                   'payload type is wrong')
-      assert_equal(msg_sizes[i], r.payload.body.length,
-                   'payload body #{i} has the wrong length')
+      assert("#{__callee__}: too many responses") { i < msg_sizes.length }
+      assert("#{__callee__}: payload body #{i} has the wrong length") do
+        msg_sizes[i] == r.payload.body.length
+      end
+      assert("#{__callee__}: payload type is wrong") do
+        :COMPRESSABLE == r.payload.type
+      end
     end
-    p 'OK: server_streaming'
+    p "OK: #{__callee__}"
   end
 
   def ping_pong
@@ -299,7 +304,7 @@ class NamedTests
     ppp = PingPongPlayer.new(msg_sizes)
     resps = @stub.full_duplex_call(ppp.each_item)
     resps.each { |r| ppp.queue.push(r) }
-    p 'OK: ping_pong'
+    p "OK: #{__callee__}"
   end
 
   def timeout_on_sleeping_server
@@ -309,7 +314,9 @@ class NamedTests
     resps.each { |r| ppp.queue.push(r) }
     fail 'Should have raised GRPC::BadStatus(DEADLINE_EXCEEDED)'
   rescue GRPC::BadStatus => e
-    assert_equal(e.code, GRPC::Core::StatusCodes::DEADLINE_EXCEEDED)
+    assert("#{__callee__}: status was wrong") do
+      e.code == GRPC::Core::StatusCodes::DEADLINE_EXCEEDED
+    end
     p "OK: #{__callee__}"
   end
 
@@ -321,8 +328,10 @@ class NamedTests
       ppp.queue.push(r)
       count += 1
     end
-    assert_equal(0, count, 'too many responses, expect 0')
-    p 'OK: empty_stream'
+    assert("#{__callee__}: too many responses expected 0") do
+      count == 0
+    end
+    p "OK: #{__callee__}"
   end
 
   def cancel_after_begin
@@ -333,9 +342,11 @@ class NamedTests
     end
     op = @stub.streaming_input_call(reqs, return_op: true)
     op.cancel
-    assert_raises(GRPC::Cancelled) { op.execute }
-    assert(op.cancelled, 'call operation should be CANCELLED')
-    p 'OK: cancel_after_begin'
+    op.execute
+    fail 'Should have raised GRPC:Cancelled'
+  rescue GRPC::Cancelled
+    assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled }
+    p "OK: #{__callee__}"
   end
 
   def cancel_after_first_response
@@ -343,10 +354,12 @@ class NamedTests
     ppp = PingPongPlayer.new(msg_sizes)
     op = @stub.full_duplex_call(ppp.each_item, return_op: true)
     ppp.canceller_op = op  # causes ppp to cancel after the 1st message
-    assert_raises(GRPC::Cancelled) { op.execute.each { |r| ppp.queue.push(r) } }
+    op.execute.each { |r| ppp.queue.push(r) }
+    fail 'Should have raised GRPC:Cancelled'
+  rescue GRPC::Cancelled
+    assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled }
     op.wait
-    assert(op.cancelled, 'call operation was not CANCELLED')
-    p 'OK: cancel_after_first_response'
+    p "OK: #{__callee__}"
   end
 
   def all
@@ -369,12 +382,15 @@ class NamedTests
     req.fill_username = fill_username
     req.fill_oauth_scope = fill_oauth_scope
     resp = @stub.unary_call(req, **kw)
-    assert_equal(:COMPRESSABLE, resp.payload.type,
-                 'large_unary: payload had the wrong type')
-    assert_equal(wanted_response_size, resp.payload.body.length,
-                 'large_unary: payload had the wrong length')
-    assert_equal(nulls(wanted_response_size), resp.payload.body,
-                 'large_unary: payload content is invalid')
+    assert('payload type is wrong') do
+      :COMPRESSABLE == resp.payload.type
+    end
+    assert('payload body has the wrong length') do
+      wanted_response_size == resp.payload.body.length
+    end
+    assert('payload body is invalid') do
+      nulls(wanted_response_size) == resp.payload.body
+    end
     resp
   end
 end