如何在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
相关产品推荐
相关产品推荐

