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

Learn about advanced features of the GripMock Embedded SDK.

Headers Matching

go
func TestAuthService_AuthenticatedEndpoint(t *testing.T) {
    // ARRANGE
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(auth.File_auth_service_proto))
    require.NoError(t, err)

    mock.Stub("AuthService", "ProtectedEndpoint").
        When(sdk.Equals("resource", "secret-data")).
        WhenHeaders(sdk.HeaderEquals("authorization", "Bearer valid-token")).
        Reply(sdk.Data("data", "secret-content")).
        Commit()

    mock.Stub("AuthService", "ProtectedEndpoint").
        When(sdk.Equals("resource", "secret-data")).
        WhenHeaders(sdk.HeaderEquals("authorization", "Bearer invalid-token")).
        ReplyError(codes.Unauthenticated, "Invalid token").
        Commit()

    client := NewAuthServiceClient(mock.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
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(external.File_external_service_proto))
    require.NoError(t, err)

    mock.Stub("ExternalService", "Process").
        When(sdk.Equals("id", "slow-request")).
        Reply(sdk.Data("result", "processed")).
        Delay(500 * time.Millisecond).
        Commit()

    client := NewExternalServiceClient(mock.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
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(user.File_user_service_proto))
    require.NoError(t, err)

    // High priority: Specific case
    mock.Stub("UserService", "GetUser").
        When(sdk.Equals("id", "special-user")).
        Reply(sdk.Data("name", "Special User", "role", "admin")).
        Priority(100).
        Commit()

    // Lower priority: General case
    mock.Stub("UserService", "GetUser").
        When(sdk.Contains("id", "")). // Matches any ID
        Reply(sdk.Data("name", "General User", "role", "user")).
        Priority(10).
        Commit()

    client := NewUserServiceClient(mock.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
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(rate.File_rate_limit_service_proto))
    require.NoError(t, err)

    // Stub matches exactly 3 times, then becomes unavailable
    mock.Stub("RateLimitService", "Call").
        When(sdk.Equals("id", "limited")).
        Reply(sdk.Data("result", "ok")).
        Times(3).
        Commit()

    client := NewRateLimitServiceClient(mock.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
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(chat.File_chat_service_proto))
    require.NoError(t, err)

    mock.Stub("ChatService", "ChatStream").
        When(sdk.Equals("roomId", "room-123")).
        ReplyStream(
            sdk.Data("message", "Hello", "sender", "Alice"),
            sdk.Data("message", "Hi there", "sender", "Bob"),
            sdk.Data("message", "Goodbye", "sender", "Alice"),
        ).
        Commit()

    client := NewChatServiceClient(mock.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
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(payment.File_payment_service_proto))
    require.NoError(t, err)

    mock.Stub("PaymentService", "Charge").
        When(sdk.Equals("amount", 0)).
        ReplyError(codes.InvalidArgument, "Amount must be greater than 0").
        Commit()

    client := NewPaymentServiceClient(mock.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")
}

Response Headers

go
func TestAuthService_LoginWithHeaders(t *testing.T) {
    // ARRANGE
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(auth.File_auth_service_proto))
    require.NoError(t, err)

    mock.Stub("AuthService", "Login").
        When(sdk.Equals("username", "test-user")).
        Reply(sdk.Data("token", "jwt-token")).
        ReplyHeaderPairs("x-session-id", "session-123", "x-permissions", "read,write").
        Commit()

    client := NewAuthServiceClient(mock.Conn())

    // ACT
    ctx := t.Context()
    reply, err := client.Login(ctx, &LoginRequest{Username: "test-user"})

    // ASSERT
    require.NoError(t, err)
    require.Equal(t, "jwt-token", reply.GetToken())
    
    // Check response headers
    trailer := metadata.MD{}
    require.NoError(t, grpc.GetTrailer(ctx, &trailer))
    require.Equal(t, []string{"session-123"}, trailer["x-session-id"])
    require.Equal(t, []string{"read,write"}, trailer["x-permissions"])
}

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.