Skip to content

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.Verify API was removed in v3.20.0. See the Upgrade Guide.

Verify that your code interacts with the mock as expected.

Call Verification

go
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:

go
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

go
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

go
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:

go
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

go
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:

go
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

go
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

go
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"])
}