You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Go中使用httptest正确Mock OpenStack API调用?

问题

我有一个使用OpenStack SDK包的函数:

import (
    "github.com/gophercloud/gophercloud"
    "github.com/gophercloud/gophercloud/openstack"
)    

func Authenticate() (*gophercloud.ProviderClient, error) {
        // 获取认证选项
        authOpts, err := openstack.AuthOptionsFromEnv()
        if err != nil {
            return nil, err
        }

        // 此处:希望Mock这个API请求
        provider, err := openstack.AuthenticatedClient(authOpts)
        if err != nil {
            return nil, err
        }
    
        // 设置60秒超时
        provider.HTTPClient = http.Client{
            Timeout: 60 * time.Second,
        }
  
        return provider, nil
    }

按照参考的测试实现方式,使用httptest需要传入server.URL(来自server := httptest.NewServer(...)创建的测试服务),但openstack.AuthenticatedClient(authOpts)是预配置的函数,我不清楚如何注入server.URL来Mock该调用并返回固定值。

参考的测试实现方式:

func TestGetFixedValue(t *testing.T) {
   server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
       if r.URL.Path != "/fixedvalue" {
           t.Errorf("Expected to request '/fixedvalue', got: %s", r.URL.Path)
       }
       if r.Header.Get("Accept") != "application/json" {
           t.Errorf("Expected Accept: application/json header, got: %s", r.Header.Get("Accept"))
       }
       w.WriteHeader(http.StatusOK)
       w.Write([]byte(`{"value":"fixed"}`))
   }))
   defer server.Close()
 
   value, _ := GetFixedValue(server.URL)
   if value != "fixed" {
       t.Errorf("Expected 'fixed', got %s", value)
   }
}

解决方案

要Mockopenstack.AuthenticatedClient的认证请求,核心是修改AuthOptions中的IdentityEndpoint,让它指向httptest创建的测试服务地址。具体步骤如下:

1. 调整原函数,支持注入AuthOptions

为了方便测试,修改Authenticate函数,允许外部传入AuthOptions(默认仍从环境变量获取):

import (
    "net/http"
    "time"

    "github.com/gophercloud/gophercloud"
    "github.com/gophercloud/gophercloud/openstack"
)

// 修改后:允许传入AuthOptions,默认从环境变量读取
func Authenticate(opts ...gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
    var authOpts gophercloud.AuthOptions
    var err error

    if len(opts) > 0 {
        authOpts = opts[0]
    } else {
        authOpts, err = openstack.AuthOptionsFromEnv()
        if err != nil {
            return nil, err
        }
    }

    provider, err := openstack.AuthenticatedClient(authOpts)
    if err != nil {
        return nil, err
    }

    provider.HTTPClient = http.Client{
        Timeout: 60 * time.Second,
    }

    return provider, nil
}

2. 编写测试用例,Mock认证服务

创建httptest服务器模拟OpenStack认证API的响应,将测试服务URL设置为AuthOptions.IdentityEndpoint后传入Authenticate函数:

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/gophercloud/gophercloud"
    "github.com/gophercloud/gophercloud/openstack"
)

func TestAuthenticate(t *testing.T) {
    // 创建测试服务器,模拟OpenStack认证成功响应
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 验证请求路径和方法是否符合OpenStack v3认证规范
        if r.URL.Path != "/v3/auth/tokens" {
            t.Errorf("Expected request path '/v3/auth/tokens', got: %s", r.URL.Path)
        }
        if r.Method != http.MethodPost {
            t.Errorf("Expected POST method, got: %s", r.Method)
        }

        // 返回模拟的认证响应,包含token和服务目录
        w.Header().Set("X-Subject-Token", "mock-token-123")
        w.WriteHeader(http.StatusCreated)
        w.Write([]byte(`{
            "token": {
                "expires_at": "2030-01-01T00:00:00Z",
                "catalog": [
                    {
                        "type": "compute",
                        "endpoints": [
                            {
                                "url": "http://mock-compute-endpoint"
                            }
                        ]
                    }
                ]
            }
        }`))
    }))
    defer server.Close()

    // 构造测试用AuthOptions,指定IdentityEndpoint为测试服务器地址
    authOpts := gophercloud.AuthOptions{
        IdentityEndpoint: server.URL + "/v3",
        Username:         "test-user",
        Password:         "test-pass",
        TenantName:       "test-tenant",
        DomainName:       "default",
    }

    // 调用Authenticate函数,传入Mock的认证选项
    provider, err := Authenticate(authOpts)
    if err != nil {
        t.Fatalf("Authenticate failed: %v", err)
    }

    // 验证返回的ProviderClient包含Mock的token
    if provider.TokenID != "mock-token-123" {
        t.Errorf("Expected token 'mock-token-123', got: %s", provider.TokenID)
    }
}

关键说明

  • openstack.AuthenticatedClient会根据AuthOptions.IdentityEndpoint指定的地址发起认证请求,因此将该地址设置为httptest服务器URL,就能把请求导向Mock服务。
  • 模拟的响应必须符合OpenStack认证API的格式,至少要包含有效的X-Subject-Token响应头和正确结构的JSON体,这样gophercloud才能正确解析生成ProviderClient。

内容的提问来源于stack exchange,提问作者HC LW

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 16:03:13