2021/04/04 gRPC+_Mockのコードリーディング
gRPCのリクエストをMockしてくれるGem
gRPCのリクエストを受け取るテストを書くのに苦労した過去があったのでこれで課題が解決するか調べる
まずはExampleのコードを読む
code:hello.proto
syntax = "proto3";
package hello;
service hello {
rpc Hello(HelloRequest) returns (HelloResponse) {}
rpc HelloServerStream(HelloRequest) returns (stream HelloStreamResponse) {}
rpc HelloClientStream(stream HelloStreamRequest) returns (HelloResponse) {}
rpc HelloStream(stream HelloStreamRequest) returns (stream HelloStreamResponse) {}
}
message HelloRequest {
string msg = 1;
}
message HelloResponse {
string msg = 1;
}
message HelloStreamRequest {
string msg = 1;
}
message HelloStreamResponse {
string msg = 1;
}
こんな感じの定義。
msgを受け取って返すシンプルなProto
このprotoから出力されるクライアント側?のpbファイルはこれ
code:hello_pb.rb
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "hello.HelloRequest" do
optional :msg, :string, 1
end
add_message "hello.HelloResponse" do
optional :msg, :string, 1
end
add_message "hello.HelloStreamRequest" do
optional :msg, :string, 1
end
add_message "hello.HelloStreamResponse" do
optional :msg, :string, 1
end
end
msgはOptionalなんだな〜。
クライアントのコードがこんな感じになってる。めちゃくちゃシンプル…というか実際の処理的なものがない。
server_pb.rb
code:hello_client.rb
require_relative './hello_services_pb'
class HelloClient
def initialize
@client = Hello::Hello::Stub.new('localhost:8000', :this_channel_is_insecure)
end
def send_message(msg, client_stream: false, server_stream: false, metadata: {})
if client_stream && server_stream
m = Hello::HelloStreamRequest.new(msg: msg)
@client.hello_stream(m.to_enum, metadata: metadata) elsif client_stream
m = Hello::HelloStreamRequest.new(msg: msg)
@client.hello_client_stream(m.to_enum, metadata: metadata) elsif server_stream
m = Hello::HelloRequest.new(msg: msg)
@client.hello_server_stream(m, metadata: metadata)
else
m = Hello::HelloRequest.new(msg: msg)
@client.hello(m, metadata: metadata)
end
end
end
hello_server_pb.rbが読み込まれている。
Streamかそれ以外で生成するインスタンスが変わるのでそこだけ、注意しておけばいいのかなー。
code:spec/grpc_mock_spec.rb
# disableじゃなくてdiable!になってるwタイポだ…。
context 'to GrpcMock.diable!' do
before do
described_class.disable!
end
it { expect { client.send_message('hello!') } .to raise_error(GRPC::Unavailable) }
context 'to GrpcMock.enable!' do
before do
described_class.enable!
end
it { expect { client.send_message('hello!') } .to raise_error(GrpcMock::NetConnectNotAllowedError) }
end
end
今日の本題じゃないけど、タイポみつけたのでPR作っておくった。
code:spec/grpc_mock_spec.rb
context 'with to_return' do
let(:response) { Hello::HelloResponse.new(msg: 'test') }
before do
described_class.enable!
GrpcMock.stub_request('/hello.hello/Hello').to_return(response)
end
it { expect(client.send_message('hello!')).to eq(response) }
end
レスポンスを返しているのをスタブしている…。やりたいことはそのStubされたものをFactoryBotみたいなモデルに積めた状態でテストしたい。
なのでそもそもの課題とこのGemが提供しているもの価値を勘違いしてた(そんな気はしていたんだけど)
ということはgRPC Server→Ruby(gRPC Client)のときに期待したレスポンスのテストが書けていればそれをモデルに詰めるのはそのモデルの責務。
実際に問題になったテストってどんなやつだったっけ?
問題のコードはFormObject的なファイルだった。
レスポンスのテストとFormObjectのテストが混ざってしまってるのが問題だったりしてそう。
エンドポイントのときのgRPCのレスポンスのテスト
エンドポイントを叩いたときの想定されるレスポンスからFormObject内で組み立てるテスト
の2つのテストを同時に行おうとして面倒なことになっている?
あんまり関係なかったかも。単純にテストの書き方が悪いかも?
FormObjectのテストがObjectを期待しているのでObjectをExpectedに書いていてそれがわかりにくくてつらい…という話だった。
共通項目とそれ以外を切り分けたい…という話だった。gRPC Mock使えばペインがマシになる…という話ではなかった。
がそれはそれとしてレスポンスやリクエストのテストはできるのでこれはこれでよさそう。