Verification v3.7.0
INFO
Minimum Requirements: Go 1.26 or later
Version history: Embedded SDK introduced in v3.7.0. Current API since v3.16.0; the legacy
sdk.Run/mock.Stub/mock.VerifyAPI was removed in v3.20.0. See the Upgrade Guide.
Verify that your code interacts with the mock as expected.
Call Verification
func TestMyService_CallVerification(t *testing.T) {
// ARRANGE
srv := sdk.NewServer(t, sdk.WithFileDescriptor(service.File_service_proto))
srv.ExpectUnary(MyService_MyMethod_FullMethodName).
Match("id", "test").
Return("result", "success")
client := NewMyServiceClient(srv.Conn())
// ACT - Make calls to the mock
_, _ = client.MyMethod(t.Context(), &MyRequest{Id: "test"})
_, _ = client.MyMethod(t.Context(), &MyRequest{Id: "test"})
// ASSERT - Verify the method was called exactly 2 times
require.Equal(t, 2, srv.Called(MyService_MyMethod_FullMethodName))
}Using Generated Full Method Constants v3.16.0
If your generated gRPC package exposes *_FullMethodName constants, you can avoid manual full-method strings:
srv.ExpectUnary(myservice.MyService_MyMethod_FullMethodName).
Match("id", "test").
Return("result", "ok")
require.Equal(t, 1, srv.Called(myservice.MyService_MyMethod_FullMethodName))Total Call Verification
func TestMyService_TotalCalls(t *testing.T) {
// ARRANGE
srv := sdk.NewServer(t, sdk.WithFileDescriptor(service.File_service_proto))
srv.ExpectUnary(MyService_MethodA_FullMethodName).
Match("id", "a").
Return("result", "A")
srv.ExpectUnary(MyService_MethodB_FullMethodName).
Match("id", "b").
Return("result", "B")
client := NewMyServiceClient(srv.Conn())
// ACT - Make calls to different methods
_, _ = client.MethodA(t.Context(), &MethodARequest{Id: "a"})
_, _ = client.MethodB(t.Context(), &MethodBRequest{Id: "b"})
_, _ = client.MethodA(t.Context(), &MethodARequest{Id: "a"})
// ASSERT - Verify total calls to all methods
require.Equal(t, 3, srv.TotalCalls()) // 2 calls to MethodA + 1 call to MethodB
}Call History
func TestMyService_History(t *testing.T) {
// ARRANGE
srv := sdk.NewServer(t, sdk.WithFileDescriptor(service.File_service_proto))
srv.ExpectUnary(MyService_MyMethod_FullMethodName).
Match("id", "tracked").
Return("result", "ok")
client := NewMyServiceClient(srv.Conn())
// ACT - Make some calls
_, _ = client.MyMethod(t.Context(), &MyRequest{Id: "tracked"})
_, _ = client.MyMethod(t.Context(), &MyRequest{Id: "tracked"})
// ASSERT - Check history
calls := srv.History()
require.Len(t, calls, 2)
require.Len(t, calls[0].Requests, 1)
require.Equal(t, "tracked", calls[0].Requests[0]["id"])
require.Equal(t, "tracked", calls[1].Requests[0]["id"])
// Verify individual calls
require.Equal(t, 2, srv.Called(MyService_MyMethod_FullMethodName))
}Context-Aware Verification and History (Remote)
In remote mode srv.Called, srv.TotalCalls, srv.ExpectationsWereMet and srv.History each become a REST call to the GripMock server, issued with t.Context().
If you need to pass a specific context (for cancellation, tracing, request-scoped values), use SDK context helpers:
func TestMyService_RemoteContextAwareChecks(t *testing.T) {
srv := sdk.NewServer(t,
sdk.WithRemote("localhost:4770", "http://localhost:4771"),
sdk.WithSession(t.Name()),
)
// ... Arrange/Act ...
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
defer cancel()
// Context-aware expectation check (honors cancellation/deadline in remote mode).
require.NoError(t, srv.ExpectationsWereMetContext(ctx))
// Recorded calls are available via srv.History(); filter by method client-side.
calls := srv.History()
require.NotEmpty(t, calls)
}ExpectationsWereMetContext is backward-compatible: in embedded mode it falls back to the non-context check.
Never Called Verification
func TestMyService_NeverCalled(t *testing.T) {
// ARRANGE
srv := sdk.NewServer(t, sdk.WithFileDescriptor(service.File_service_proto))
srv.ExpectUnary(MyService_UsedMethod_FullMethodName).
Match("id", "used").
Return("result", "success")
// Don't define stub for UnusedMethod - it shouldn't be called
client := NewMyServiceClient(srv.Conn())
// ACT - Only call the used method
_, _ = client.UsedMethod(t.Context(), &UsedMethodRequest{Id: "used"})
// ASSERT - Verify the unused method was never called
require.Equal(t, 0, srv.Called(MyService_UnusedMethod_FullMethodName))
// Also verify the used method was called once
require.Equal(t, 1, srv.Called(MyService_UsedMethod_FullMethodName))
}Automatic Verification with Times
When using the Times feature, the SDK automatically verifies that the expected number of calls were made:
func TestMyService_TimesVerification(t *testing.T) {
// ARRANGE
srv := sdk.NewServer(t, sdk.WithFileDescriptor(service.File_service_proto))
// Stub that should be called exactly 2 times
srv.ExpectUnary(MyService_MyMethod_FullMethodName).
Match("id", "limited").
Times(2). // Expected to be called exactly 2 times
Return("result", "ok")
client := NewMyServiceClient(srv.Conn())
// ACT - Make exactly 2 calls as specified in Times(2)
_, _ = client.MyMethod(t.Context(), &MyRequest{Id: "limited"})
_, _ = client.MyMethod(t.Context(), &MyRequest{Id: "limited"})
// ASSERT - ExpectationsWereMet() runs from the t.Cleanup that NewServer registers
// To verify explicitly:
require.NoError(t, srv.ExpectationsWereMet())
}Complex Verification Scenario
func TestOrderService_ComplexVerification(t *testing.T) {
// ARRANGE
srv := sdk.NewServer(t, sdk.WithFileDescriptor(order.File_order_service_proto))
// Create stubs with different call limits
srv.ExpectUnary(OrderService_CreateOrder_FullMethodName).
Match("userId", "premium").
Times(1). // Should be called exactly once
Return("orderId", "ORD-001")
srv.ExpectUnary(OrderService_CancelOrder_FullMethodName).
Match("orderId", "ORD-001").
Times(1). // Should be called exactly once
Return("status", "cancelled")
srv.ExpectUnary(OrderService_GetOrder_FullMethodName).
Match("orderId", "ORD-001").
Times(2). // Should be called exactly twice
Return("status", "active")
client := NewOrderServiceClient(srv.Conn())
// ACT
// Create order
createResp, err := client.CreateOrder(t.Context(), &CreateOrderRequest{UserId: "premium"})
require.NoError(t, err)
// Check order status twice
_, err = client.GetOrder(t.Context(), &GetOrderRequest{OrderId: "ORD-001"})
require.NoError(t, err)
_, err = client.GetOrder(t.Context(), &GetOrderRequest{OrderId: "ORD-001"})
require.NoError(t, err)
// Cancel order
_, err = client.CancelOrder(t.Context(), &CancelOrderRequest{OrderId: "ORD-001"})
require.NoError(t, err)
// ASSERT - Times() is checked by the registered t.Cleanup
require.Equal(t, "ORD-001", createResp.GetOrderId())
// Or verify explicitly:
require.NoError(t, srv.ExpectationsWereMet())
}Verification with History Analysis
func TestPaymentService_HistoryAnalysis(t *testing.T) {
// ARRANGE
srv := sdk.NewServer(t, sdk.WithFileDescriptor(payment.File_payment_service_proto))
srv.ExpectUnary(PaymentService_ProcessPayment_FullMethodName).
Match("amount", float64(100)).
Return("transactionId", "TXN-100")
srv.ExpectUnary(PaymentService_ProcessPayment_FullMethodName).
Match("amount", float64(200)).
Return("transactionId", "TXN-200")
client := NewPaymentServiceClient(srv.Conn())
// ACT
_, _ = client.ProcessPayment(t.Context(), &ProcessPaymentRequest{Amount: 100})
_, _ = client.ProcessPayment(t.Context(), &ProcessPaymentRequest{Amount: 200})
_, _ = client.ProcessPayment(t.Context(), &ProcessPaymentRequest{Amount: 100})
// ASSERT
// Check total calls
require.Equal(t, 3, srv.TotalCalls())
// Check specific method calls
require.Equal(t, 3, srv.Called(PaymentService_ProcessPayment_FullMethodName))
// Analyze history
allCalls := srv.History()
require.Len(t, allCalls, 3)
for _, call := range allCalls {
require.Len(t, call.Requests, 1)
require.Len(t, call.Responses, 1)
}
// Verify specific call details
require.Equal(t, float64(100), allCalls[0].Requests[0]["amount"])
require.Equal(t, "TXN-100", allCalls[0].Responses[0]["transactionId"])
require.Equal(t, float64(200), allCalls[1].Requests[0]["amount"])
require.Equal(t, "TXN-200", allCalls[1].Responses[0]["transactionId"])
require.Equal(t, float64(100), allCalls[2].Requests[0]["amount"])
require.Equal(t, "TXN-100", allCalls[2].Responses[0]["transactionId"])
}