View Source MockGRPC (MockGRPC v0.2.0)
MockGRPC is a library for defining concurrent client mocks for gRPC Elixir.
It works by implementing a client adapter that intercepts requests using the mocks you defined, and performing expectations on them.
usage
Usage
Imagine that you have a module calling a say_hello
RPC.
defmodule Demo do
def say_hello(name) do
{:ok, channel} = GRPC.Stub.connect("localhost:50051")
GreetService.Stub.say_hello(channel, %SayHelloRequest{name: "John Doe"})
end
end
The first step is to change the connect
code to use an adapter coming from the app environment, so that
you can use MockGRPC
in test mode, and the default adapter in dev and production.
{:ok, channel} =
GRPC.Stub.connect(
"localhost:50051",
adapter: Application.get_env(:demo, :grpc_adapter)
)
Or if you're using ConnGRPC
, add adapter
to the channel opts
.
Then, on your config/test.exs
, set it to MockGRPC.Adapter
:
Application.put_env(:demo, :grpc_adapter, MockGRPC.Adapter)
Now it's time to write your test. To enable mocks, add use MockGRPC
to your test, and call
MockGRPC.expect/2
or MockGRPC.expect/3
to set expectations.
defmodule DemoTest do
use ExUnit.Case, async: true
use MockGRPC
test "say_hello/1" do
MockGRPC.expect(&GreetService.Stub.say_hello/2, fn req ->
assert %SayHelloRequest{name: "John Doe"} == req
{:ok, %SayHelloResponse{message: "Hello John Doe"}}
end)
assert {:ok, %SayHelloResponse{message: "Hello John Doe"}} = Demo.say_hello("John Doe")
end
end
All expectations are defined based on the current process. This means that if you call gRPC from
a separate process, and this process is not a Task
*, it won't have access to the expectations
by default. But there are ways to overcome that. See the "Multi-process collaboration" section.
*Task
is supported automatically with no extra code needed due to its native
caller tracking.
multi-process-collaboration
Multi-process collaboration
MockGRPC supports multi-process collaboration via two mechanisms:
- manually set context
- global mode
manually-set-context
Manually set context
In order for other processes to have access to your mocks, you can call set_context/1
on the external process passing the PID of the test.
global-mode
Global mode
To support global mode, set your test async
option to false
. However, this won't allow
your test file to execute in parallel with other tests.
Link to this section Summary
Functions
Makes GRPC.Stub.connect/2
return an error tuple, simulating that the server is down.
Adds an expectation using a gRPC service function capture.
Adds an expectation using the gRPC service module and function name.
Set mock context, in case the mock is being called from another process in an async test.
Makes GRPC.Stub.connect/2
return successfully, reverting the down/0
call.
Link to this section Functions
Makes GRPC.Stub.connect/2
return an error tuple, simulating that the server is down.
If you're using ConnGRPC
, you will need additional code to make it work.
See ConnGRPC - Simulating unavailable channel
Adds an expectation using a gRPC service function capture.
Example:
MockGRPC.expect(&GreetService.Stub.say_hello/2, fn req ->
assert %SayHelloRequest{name: "John Doe"} == req
{:ok, %SayHelloResponse{message: "Hello John Doe"}}
end)
assert {:ok, %SayHelloResponse{message: "Hello John Doe"}} = Demo.say_hello("John Doe")
Adds an expectation using the gRPC service module and function name.
Example:
MockGRPC.expect(GreetService.Service, :say_hello, fn req ->
assert %SayHelloRequest{name: "John Doe"} == req
{:ok, %SayHelloResponse{message: "Hello John Doe"}}
end)
assert {:ok, %SayHelloResponse{message: "Hello John Doe"}} = Demo.say_hello("John Doe")
Set mock context, in case the mock is being called from another process in an async test.
This is not needed for Task
processes.
Example:
test "calling a mock from a different process" do
parent = self()
MockGRPC.expect(&GreetService.Stub.say_hello/2, fn req ->
assert %SayHelloRequest{name: "John Doe"} == req
{:ok, %SayHelloResponse{message: "Hello John Doe"}}
end)
# This is just an example to demonstrate the concept. In a real world scenario, you'd
# be better off using the `Task` module, which doesn't require calling `set_context`
spawn(fn ->
# Ensure this process has access to the mocks
MockGRPC.set_context(parent)
{:ok, channel} = GRPC.Stub.connect("localhost:50051", adapter: Application.get_env(:demo, :grpc_adapter))
response = GreetService.Stub.say_hello(channel, %SayHelloRequest{name: "John Doe"})
# Do the assertion outside the process, to avoid a race condition where the test
# finishes before this process completes execution.
send(parent, {:my_process_result, response})
end)
assert_receive {:my_process_result, %SayHelloResponse{message: "Hello John Doe"}}
end
Makes GRPC.Stub.connect/2
return successfully, reverting the down/0
call.