Skip to content

Advanced Features v3.7.0

WARNING

⚠️ EXPERIMENTAL FEATURE: The GripMock Embedded SDK is currently experimental. The API is subject to change without notice, and functionality may be modified in future versions. Use at your own risk.

INFO

Minimum Requirements: Go 1.26 or later

Version history: All features existed in v3.7.0 (legacy API). Current v2 API since v3.16.0. See the Upgrade Guide for migration.

Learn about advanced features of the GripMock Embedded SDK.


Legacy API (v3.7.0+)

The same features in the legacy API:

go
mock.Stub(sdk.By(FullMethod)).
    WhenHeaders(sdk.Equals("authorization", "Bearer token")).
    Reply(sdk.Data("result", "ok")).
    Delay(100 * time.Millisecond).
    Priority(10).
    Times(3).
    Commit()

Headers Matching

go
func TestAuthService_AuthenticatedEndpoint(t *testing.T) {
    // ARRANGE
    srv := sdk.NewServer(t, sdk.WithFileDescriptor(auth.File_auth_service_proto))
    defer srv.Close()

    srv.ExpectUnary(AuthService_ProtectedEndpoint_FullMethodName).
        Match("resource", "secret-data").
        WithHeader(sdk.Contains("authorization", "Bearer valid-token")).
        Return("data", "secret-content")

    srv.ExpectUnary(AuthService_ProtectedEndpoint_FullMethodName).
        Match("resource", "secret-data").
        WithHeader(sdk.Contains("authorization", "Bearer invalid-token")).
        ReturnError(codes.Unauthenticated, "Invalid token")

    client := NewAuthServiceClient(srv.Conn())

    // ACT - Valid token
    validCtx := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("authorization", "Bearer valid-token"))
    validReply, validErr := client.ProtectedEndpoint(validCtx, &ProtectedEndpointRequest{Resource: "secret-data"})

    // ACT - Invalid token
    invalidCtx := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("authorization", "Bearer invalid-token"))
    _, invalidErr := client.ProtectedEndpoint(invalidCtx, &ProtectedEndpointRequest{Resource: "secret-data"})

    // ASSERT
    require.NoError(t, validErr)
    require.Equal(t, "secret-content", validReply.GetData())
    require.Error(t, invalidErr)
    require.Equal(t, codes.Unauthenticated, status.Code(invalidErr))
}

Delays

go
func TestExternalService_SlowResponse(t *testing.T) {
    // ARRANGE
    srv := sdk.NewServer(t, sdk.WithFileDescriptor(external.File_external_service_proto))
    defer srv.Close()

    srv.ExpectUnary(ExternalService_Process_FullMethodName).
        Match("id", "slow-request").
        Return(Delay(500*time.Millisecond, "result", "processed"))

    client := NewExternalServiceClient(srv.Conn())

    // ACT
    start := time.Now()
    reply, err := client.Process(t.Context(), &ProcessRequest{Id: "slow-request"})
    elapsed := time.Since(start)

    // ASSERT
    require.NoError(t, err)
    require.Equal(t, "processed", reply.GetResult())
    require.GreaterOrEqual(t, elapsed, 500*time.Millisecond)
}

Priority

go
func TestUserService_GetUser_Priority(t *testing.T) {
    // ARRANGE
    srv := sdk.NewServer(t, sdk.WithFileDescriptor(user.File_user_service_proto))
    defer srv.Close()

    // High priority: Specific case
    srv.ExpectUnary(UserService_GetUser_FullMethodName).
        Match("id", "special-user").
        Priority(100).
        Return("name", "Special User", "role", "admin")

    // Lower priority: General case
    srv.ExpectUnary(UserService_GetUser_FullMethodName).
        Match(sdk.Contains("id", "")). // Matches any ID
        Priority(10).
        Return("name", "General User", "role", "user")

    client := NewUserServiceClient(srv.Conn())

    // ACT
    specialReply, err1 := client.GetUser(t.Context(), &GetUserRequest{Id: "special-user"})
    generalReply, err2 := client.GetUser(t.Context(), &GetUserRequest{Id: "regular-user"})

    // ASSERT
    require.NoError(t, err1)
    require.NoError(t, err2)
    require.Equal(t, "Special User", specialReply.GetName())
    require.Equal(t, "admin", specialReply.GetRole())
    require.Equal(t, "General User", generalReply.GetName())
    require.Equal(t, "user", generalReply.GetRole())
}

Call Limits (Times)

go
func TestRateLimitService_LimitedCalls(t *testing.T) {
    // ARRANGE
    srv := sdk.NewServer(t, sdk.WithFileDescriptor(rate.File_rate_limit_service_proto))
    defer srv.Close()

    // Stub matches exactly 3 times, then becomes unavailable
    srv.ExpectUnary(RateLimitService_Call_FullMethodName).
        Match("id", "limited").
        Times(3).
        Return("result", "ok")

    client := NewRateLimitServiceClient(srv.Conn())

    // ACT
    reply1, err1 := client.Call(t.Context(), &CallRequest{Id: "limited"})
    reply2, err2 := client.Call(t.Context(), &CallRequest{Id: "limited"})
    reply3, err3 := client.Call(t.Context(), &CallRequest{Id: "limited"})
    _, err4 := client.Call(t.Context(), &CallRequest{Id: "limited"}) // Should fail

    // ASSERT
    require.NoError(t, err1)
    require.NoError(t, err2)
    require.NoError(t, err3)
    require.Error(t, err4) // Should fail after 3 calls
    require.Equal(t, "ok", reply1.GetResult())
    require.Equal(t, "ok", reply2.GetResult())
    require.Equal(t, "ok", reply3.GetResult())
}

Streaming Support

go
func TestChatService_ServerStreaming(t *testing.T) {
    // ARRANGE
    srv := sdk.NewServer(t, sdk.WithFileDescriptor(chat.File_chat_service_proto))
    defer srv.Close()

    srv.ExpectServerStream(ChatService_ChatStream_FullMethodName).
        Match("roomId", "room-123").
        SendStream(
            map[string]any{"message": "Hello", "sender": "Alice"},
            map[string]any{"message": "Hi there", "sender": "Bob"},
            map[string]any{"message": "Goodbye", "sender": "Alice"},
        )

    client := NewChatServiceClient(srv.Conn())

    // ACT
    stream, err := client.ChatStream(t.Context(), &ChatStreamRequest{RoomId: "room-123"})
    require.NoError(t, err)

    var messages []ChatMessage
    for {
        msg, err := stream.Recv()
        if err == io.EOF {
            break
        }
        require.NoError(t, err)
        messages = append(messages, *msg)
    }

    // ASSERT
    require.Len(t, messages, 3)
    require.Equal(t, "Hello", messages[0].GetMessage())
    require.Equal(t, "Alice", messages[0].GetSender())
    require.Equal(t, "Hi there", messages[1].GetMessage())
    require.Equal(t, "Bob", messages[1].GetSender())
    require.Equal(t, "Goodbye", messages[2].GetMessage())
    require.Equal(t, "Alice", messages[2].GetSender())
}

Error Responses

go
func TestPaymentService_Failure(t *testing.T) {
    // ARRANGE
    srv := sdk.NewServer(t, sdk.WithFileDescriptor(payment.File_payment_service_proto))
    defer srv.Close()

    srv.ExpectUnary(PaymentService_Charge_FullMethodName).
        Match("amount", 0).
        ReturnError(codes.InvalidArgument, "Amount must be greater than 0")

    client := NewPaymentServiceClient(srv.Conn())

    // ACT
    _, err = client.Charge(t.Context(), &ChargeRequest{Amount: 0})

    // ASSERT
    require.Error(t, err)
    require.Equal(t, codes.InvalidArgument, status.Code(err))
    require.Contains(t, err.Error(), "Amount must be greater than 0")
}

WARNING

⚠️ EXPERIMENTAL FEATURE: The GripMock Embedded SDK is currently experimental. The API is subject to change without notice, and functionality may be modified in future versions. Use at your own risk.