Skip to content

Defining Stubs 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

The SDK provides helper functions to define stubs easily.

Basic Matching

go
func TestUserService_GetUser(t *testing.T) {
    // ARRANGE
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(user.File_user_service_proto))
    require.NoError(t, err)

    // Define stubs in the Arrange phase
    mock.Stub("UserService", "GetUser").
        When(sdk.Equals("id", "user-123")).
        Reply(sdk.Data("name", "John Doe", "email", "john@example.com")).
        Commit()

    client := NewUserServiceClient(mock.Conn())

    // ACT
    reply, err := client.GetUser(t.Context(), &GetUserRequest{Id: "user-123"})

    // ASSERT
    require.NoError(t, err)
    require.Equal(t, "John Doe", reply.GetName())
    require.Equal(t, "john@example.com", reply.GetEmail())
}

Multiple Matching Strategies

go
func TestUserService_SearchUsers(t *testing.T) {
    // ARRANGE
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(user.File_user_service_proto))
    require.NoError(t, err)

    // Exact match stub
    mock.Stub("UserService", "SearchUsers").
        When(sdk.Equals("name", "exact-match")).
        Reply(sdk.Data("results", []any{
            map[string]any{"id": "1", "name": "exact-match"},
        })).
        Commit()

    // Partial match stub
    mock.Stub("UserService", "SearchUsers").
        When(sdk.Contains("name", "partial")).
        Reply(sdk.Data("results", []any{
            map[string]any{"id": "2", "name": "partial-result"},
        })).
        Commit()

    // Regex match stub
    mock.Stub("UserService", "SearchUsers").
        When(sdk.Matches("email", `^[a-zA-Z0-9._%+-]+@example\.com$`)).
        Reply(sdk.Data("results", []any{
            map[string]any{"id": "3", "name": "regex-match"},
        })).
        Commit()

    client := NewUserServiceClient(mock.Conn())

    // ACT
    exactReply, err := client.SearchUsers(t.Context(), &SearchUsersRequest{Name: "exact-match"})
    require.NoError(t, err)

    partialReply, err := client.SearchUsers(t.Context(), &SearchUsersRequest{Name: "partial-search"})
    require.NoError(t, err)

    regexReply, err := client.SearchUsers(t.Context(), &SearchUsersRequest{Email: "test@example.com"})
    require.NoError(t, err)

    // ASSERT
    require.Equal(t, "1", exactReply.Results[0].Id)
    require.Equal(t, "2", partialReply.Results[0].Id)
    require.Equal(t, "3", regexReply.Results[0].Id)
}

Real-World Example

Here's a more complete example based on actual usage:

go
import (
    "testing"
    "time"

    "github.com/stretchr/testify/require"
    sdk "github.com/bavix/gripmock/v3/pkg/sdk"
)

func TestGreeter_SayHello(t *testing.T) {
    // ARRANGE
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(helloworld.File_helloworld_proto))
    require.NoError(t, err)
    
    // Define a stub with delay
    delayMs := 20
    mock.Stub("helloworld.Greeter", "SayHello").
        When(sdk.Equals("name", "Bob")).
        Reply(sdk.Data("message", "Hello Bob")).
        Delay(time.Duration(delayMs) * time.Millisecond).
        Commit()

    client := helloworld.NewGreeterClient(mock.Conn())

    // ACT
    start := time.Now()
    reply, err := client.SayHello(t.Context(), &helloworld.HelloRequest{Name: "Bob"})
    elapsed := time.Since(start)

    // ASSERT
    require.NoError(t, err)
    require.Equal(t, "Hello Bob", reply.GetMessage())
    require.GreaterOrEqual(t, elapsed.Milliseconds(), int64(delayMs))
}

Dynamic Template Example

go
func TestGreeter_SayHello_DynamicTemplate(t *testing.T) {
    // ARRANGE
    mock, err := sdk.Run(t, sdk.WithFileDescriptor(helloworld.File_helloworld_proto))
    require.NoError(t, err)
    
    // Use dynamic template to return request data in response
    mock.Stub("helloworld.Greeter", "SayHello").
        When(sdk.Matches("name", ".+")).
        Reply(sdk.Data("message", "Hi {{.Request.name}}")).
        Commit()

    client := helloworld.NewGreeterClient(mock.Conn())

    // ACT
    reply, err := client.SayHello(t.Context(), &helloworld.HelloRequest{Name: "Alex"})

    // ASSERT
    require.NoError(t, err)
    require.Equal(t, "Hi Alex", reply.GetMessage())
}

Advanced Options

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

    // With headers matching
    mock.Stub("AuthService", "Login").
        When(sdk.Equals("username", "test-user")).
        WhenHeaders(sdk.HeaderEquals("authorization", "Bearer valid-token")).
        Reply(sdk.Data("token", "jwt-token-here")).
        Commit()

    // With call limit (Times)
    mock.Stub("AuthService", "Login").
        When(sdk.Equals("username", "limited-user")).
        Reply(sdk.Data("token", "limited-token")).
        Times(2). // Stub will only match 2 times
        Commit()

    client := NewAuthServiceClient(mock.Conn())

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

    limitedReply1, err := client.Login(t.Context(), &LoginRequest{Username: "limited-user"})
    require.NoError(t, err)

    limitedReply2, err := client.Login(t.Context(), &LoginRequest{Username: "limited-user"})
    require.NoError(t, err)

    // Third call to limited-user should fail since Times(2)
    _, err = client.Login(t.Context(), &LoginRequest{Username: "limited-user"})
    require.Error(t, err)

    // ASSERT
    require.Equal(t, "jwt-token-here", authorizedReply.GetToken())
    require.Equal(t, "limited-token", limitedReply1.GetToken())
    require.Equal(t, "limited-token", limitedReply2.GetToken())
}

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.