如何在 Golang 的集成测试中模拟外部 HTTP 请求 API?

huangapple go评论81阅读模式
英文:

How to mock external http request api when integration test in golang

问题

我有一个服务,可以从数据库获取数据,并从第三方API获取其他数据。

像这样:

type Service interface {
     GetDataFromDB(params apiParams, thirdClient ApiCient)
}

type Repository interface {
     GetDataFromDB(orm *gorm.DB)
}
 
type DataService struct {
     repo Repository
}

func (s *DataService) GetDataFromDB(params apiParams, thirdClient ApiClient) []interface{} {
     var result []interface{}
     dataFromDb := s.repo.GetDataFromDB()
     dataFromAPI := thirdClient.Do(url)
     result = append(result, dataFromDb)
     result = append(result, dataFromAPI)
     return result
 
}


func getData(c *gin.Context) {
     //已经实现了接口
     repo := NewRepository(orm)
     srv := NewService(repo)
     thirdPartyClient := NewApiClient()
     params := &params{Id:1,Name:"hello world"}
     res := srv.GetDataFromDB(params, thirdPartyClient)
     c.JSON(200,res)
}

func TestGetData(t *testing.T) {
     w := httptest.NewRecorder()
	 request := http.NewRequest(http.MethodGet, "/v1/get_data", nil)
	 route.ServeHTTP(w, request)
}

第三方API客户端将返回随机数据。
在这种情况下,我应该怎么做?
如果我想模拟客户端以获取稳定的数据进行测试,如何在集成测试中伪造它?

英文:

I have a service to get db data and get others data from third party api.

Like this:

type Service interface {
     GetDataFromDB(params apiParams, thirdClient ApiCient)
}

type Repository interface {
     GetDataFromDB(orm *gorm.DB)
}
 
type DataService struct {
     repo Repository
}

func (s *DataService) GetDataFromDB(params apiParams, thirdClient ApiClient) []interface{} {
     var result []interface{}
     dataFromDb := s.repo.GetDataFromDB()
     dataFromAPI := thirdClient.Do(url)
     result = append(result, dataFromDb)
     result = append(result, dataFromAPI)
     return result
 
}


func getData(c *gin.Context) {
     //already implement interface 
     repo := NewRepository(orm)
     srv := NewService(repo)
     thirdPartyClient := NewApiClient()
     params := &params{Id:1,Name:"hello world"}
     res := srv.GetDataFromDB(params, thirdPartyClient)
     c.JSON(200,res)
}

func TestGetData(t *testing.T) {
     w := httptest.NewRecorder()
	 request := http.NewRequest(http.MethodGet, "/v1/get_data", nil)
	 route.ServeHTTP(w, request)
}

And third party api client will return random data.
<br>
In this situation, what should I do ?
<br>
If I want to mock client to get stable data to test, how to fake it in integration test ?

答案1

得分: 2

我假设“集成测试”意味着您将运行整个应用程序,然后与其依赖项一起测试运行实例(在您的情况下是数据库和第三方服务)。我假设您不是指单元测试。

对于集成测试,您有几个选项。在我的情况下,通常我会进行集成测试,包括与第三方客户端连接的内容(不使用模拟),因为我想测试我的服务与第三方服务的集成。或者,如果不可能的话,我可能会编写一个简单的存根应用程序,其公共接口与第三方服务相同,并在本地主机(或其他地方)上运行它,以供我的应用程序在测试期间连接。

如果您不想或无法执行上述任何一种方法,并且想在Go应用程序中存根外部依赖项,您可以为第三方客户端编写一个接口,并在运行集成测试时提供接口的存根实现(使用应用程序上的标志告诉它以“存根”模式运行或类似的方式)。

以下是一个可能的示例。这是您发布的源代码文件,但使用了一个用于获取第三方数据的接口:

type Service interface {
     GetDataFromDB(params apiParams, thirdClient ApiCient)
}

type Repository interface {
     GetDataFromDB(orm *gorm.DB)
}

type ThirdPartyDoer interface {
    Do(url string) interface{}
}
 
type DataService struct {
     repo Repository
     thirdParty ThirdPartyDoer
}

func (s *DataService) GetDataFromDB(params apiParams, thirdClient ApiClient) []interface{} {
     var result []interface{}
     dataFromDb := s.repo.GetDataFromDB()
     dataFromAPI := s.thirdParty.Do(url)
     result = append(result, dataFromDb)
     result = append(result, dataFromAPI)
     return result
 
}

然后,您可以编写ThirdPartyDoer的存根实现,并在测试时使用它。在生产环境中,您可以使用真实的第三方客户端作为ThirdPartyDoer的实现:

type thirdPartyDoerStub struct {}

func (s *thirdPartyDoerStub) Do(url string) interface{} {
   return "some static test data"
}

// ...

// 测试设置:

integrationTestDataService := &DataService{repo: realRepository, thirdParty: &thirdPartyDoerStub{}}


// 生产设置:

integrationTestDataService := &DataService{repo: realRepository, thirdParty: realThirdParty}

在启动应用程序时,您需要有一个标志来选择“测试设置”和“生产设置”之间的模式。

英文:

I am assuming "integration test" means you will be running your entire application and then testing the running instance together with its dependencies (in your case a database & third party service). I assume you do not mean unit testing.

For integration tests you have a few options. In my case, usually I would integration test including whatever the third party client is connecting to (no mocking) because I want to test the integration of my service with the third party one. Or if that is not possible I might write a simple stub application with the same public interface as the third party service and run it on localhost (or somewhere) for my application to connect to during testing.

If you don't want to or can't do either of those and want to stub the external dependency inside your Go application, you can write an interface for the third party client and provide a stubbed implementation of the interface when running integration tests (using a flag on your application to tell it to run in "stubbed" mode or something of that nature).

Here's an example of what this might look like. Here's the source code file you posted - but using an interface for getting the third party data:

type Service interface {
     GetDataFromDB(params apiParams, thirdClient ApiCient)
}

type Repository interface {
     GetDataFromDB(orm *gorm.DB)
}

type ThirdPartyDoer interface {
    Do(url string) interface{}
}
 
type DataService struct {
     repo Repository
     thirdParty ThirdPartyDoer
}

func (s *DataService) GetDataFromDB(params apiParams, thirdClient ApiClient) []interface{} {
     var result []interface{}
     dataFromDb := s.repo.GetDataFromDB()
     dataFromAPI := s.thirdParty.Do(url)
     result = append(result, dataFromDb)
     result = append(result, dataFromAPI)
     return result
 
}

Then you can write a stub implementation for ThirdPartyDoer and use it when testing. When running in Production you can use the real third party client as ThirdPartyDoer's implementation:

type thirdPartyDoerStub struct {}

func (s *thirdPartyDoerStub) Do(url string) interface{} {
   return &quot;some static test data&quot;
}

// ...

// Test setup:

integrationTestDataService := &amp;DataService{repo: realRepository, thirdParty: &amp;thirdPartyDoerStub{}}


// Production setup:

integrationTestDataService := &amp;DataService{repo: realRepository, thirdParty: realThirdParty}

You would need to have a flag to select between "test setup" and "production setup" when starting your application.

huangapple
  • 本文由 发表于 2021年12月23日 13:00:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/70457829.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定