如何使用多种方法模拟第三方结构并在依赖于第三方结构的端点上执行单元测试?
How to mock a third-party struct with many methods and perform unit tests on endpoints that depend on the third-party struct?
我在 gin gonic 中使用 getstream 的 Go 库并意识到我的端点将严重依赖于 stream_chat.Client
。
例如,在以下端点 (/v1/chat/test-token
) 中,必须创建一个 stream_chat.Client
,因此在单元测试中测试此端点将意味着创建和维护一个记录我使用的所有方法的接口从 stream_chat.Client
开始,这样我就可以使用满足相同接口的 MockClient
执行依赖注入,然后我可以在编写单元测试时模拟方法 chatClient.UpsertUser
和 chatClient.CreateToken
。
func main() {
config.Load()
server := gin.New()
chatClient, err := stream_chat.NewClient(config.StreamApiKey, config.StreamApiSecret)
if err != nil {
log.Err(err)
os.Exit(2)
}
v1 := server.Group("/v1")
{
v1.GET("/chat/test-token/", func(c *gin.Context) {
_, err := chatClient.UpsertUser(&stream.User{
ID: "test-user",
Role: "admin",
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{})
}
token, _ := chatClient.CreateToken("test-user", time.Time{})
c.JSON(http.StatusOK, gin.H{
"token": token,
})
})
}
server.Run(fmt.Sprintf(":%s", config.Port))
}
在我看来,为了在端点上保持良好的测试覆盖率,记录我从 stream_chat.Client
开始使用的每一种方法非常费力,所以我想知道在这种情况下应该怎么做?
- 为
stream_chat.Client
维护接口是否正确?
- 不太相关:有没有办法将
gin.HandlerFunc
,即 func(c *gin.Context)
与 stream_chat.Client
的创建正确分离?
- 更不相关:创建一个单例更好吗
stream_chat.Client
或者我应该为每个需要客户端的端点创建一个新客户端?
Is maintaining an interface for stream_chat.Client the correct way to go?
如果您有一个 non-interface 依赖项并且您希望用它来对处理程序进行单元测试,那么是的。您需要将 stream_chat.Client
包装在一个界面中。
如果 third-party 结构有很多方法,您可以将接口拆分为逻辑单元,并仅在每个处理程序中注入实际需要的那些。底层 stream_chat.Client
实现了所有这些,但是可以使各个模拟保持较小并且更易于推理。就个人而言,我认为这不值得开销。有很多 open-source 模拟生成器,最重要的是 mock
and mockgen
,还有从结构生成接口的工具。
Is there a way to properly decouple the gin.HandlerFunc, i.e. func(c *gin.Context) from the creation of stream_chat.Client?
您有多种选择,您可以在此处找到:
简而言之,我更喜欢的选项是因为更好 unit-testability 是:
- 创建结构的处理程序方法和该结构的依赖项字段。
- 使用提供者模式并在中间件中将提供者设置到 Gin 上下文中
我在 gin gonic 中使用 getstream 的 Go 库并意识到我的端点将严重依赖于 stream_chat.Client
。
例如,在以下端点 (/v1/chat/test-token
) 中,必须创建一个 stream_chat.Client
,因此在单元测试中测试此端点将意味着创建和维护一个记录我使用的所有方法的接口从 stream_chat.Client
开始,这样我就可以使用满足相同接口的 MockClient
执行依赖注入,然后我可以在编写单元测试时模拟方法 chatClient.UpsertUser
和 chatClient.CreateToken
。
func main() {
config.Load()
server := gin.New()
chatClient, err := stream_chat.NewClient(config.StreamApiKey, config.StreamApiSecret)
if err != nil {
log.Err(err)
os.Exit(2)
}
v1 := server.Group("/v1")
{
v1.GET("/chat/test-token/", func(c *gin.Context) {
_, err := chatClient.UpsertUser(&stream.User{
ID: "test-user",
Role: "admin",
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{})
}
token, _ := chatClient.CreateToken("test-user", time.Time{})
c.JSON(http.StatusOK, gin.H{
"token": token,
})
})
}
server.Run(fmt.Sprintf(":%s", config.Port))
}
在我看来,为了在端点上保持良好的测试覆盖率,记录我从 stream_chat.Client
开始使用的每一种方法非常费力,所以我想知道在这种情况下应该怎么做?
- 为
stream_chat.Client
维护接口是否正确? - 不太相关:有没有办法将
gin.HandlerFunc
,即func(c *gin.Context)
与stream_chat.Client
的创建正确分离? - 更不相关:创建一个单例更好吗
stream_chat.Client
或者我应该为每个需要客户端的端点创建一个新客户端?
Is maintaining an interface for stream_chat.Client the correct way to go?
如果您有一个 non-interface 依赖项并且您希望用它来对处理程序进行单元测试,那么是的。您需要将 stream_chat.Client
包装在一个界面中。
如果 third-party 结构有很多方法,您可以将接口拆分为逻辑单元,并仅在每个处理程序中注入实际需要的那些。底层 stream_chat.Client
实现了所有这些,但是可以使各个模拟保持较小并且更易于推理。就个人而言,我认为这不值得开销。有很多 open-source 模拟生成器,最重要的是 mock
and mockgen
,还有从结构生成接口的工具。
Is there a way to properly decouple the gin.HandlerFunc, i.e. func(c *gin.Context) from the creation of stream_chat.Client?
您有多种选择,您可以在此处找到:
简而言之,我更喜欢的选项是因为更好 unit-testability 是:
- 创建结构的处理程序方法和该结构的依赖项字段。
- 使用提供者模式并在中间件中将提供者设置到 Gin 上下文中