英文:
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
更好,还是应该为每个需要客户端的端点创建一个新的客户端?
英文:
I'm working with getstream's Go library in gin gonic and realized that my endpoints will be heavily dependent on stream_chat.Client
.
For instance, in the following endpoint (/v1/chat/test-token
), a stream_chat.Client
must be created so testing this endpoint in unit test would mean creating and maintaining an interface that documents all the methods I use from stream_chat.Client
so that I can perform dependency injection with a MockClient
that satisfies the same interface and then I can mock the methods chatClient.UpsertUser
and chatClient.CreateToken
when I write my unit test.
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))
}
It seems to me to be quite laborious to document each method that I'd use from stream_chat.Client
in order to keep a good test coverage on the endpoints, so I wonder what one should do in this case?
- Is maintaining an interface for
stream_chat.Client
the correct way to go? - Less relevant: Is there a way to properly decouple the
gin.HandlerFunc
, i.e.func(c *gin.Context)
from the creation ofstream_chat.Client
? - Even less relevant: Is it better to create a singleton
stream_chat.Client
or should I create a new client for each endpoint that requires a client?
答案1
得分: 4
维护一个用于 stream_chat.Client 的接口是否是正确的做法?
如果你有一个非接口的依赖项,并且你希望使用它来对处理程序进行单元测试,那么是的。你需要在一个接口中包装 stream_chat.Client
。
如果第三方结构体有很多方法,你可以将接口拆分为逻辑单元,并且只在每个处理程序中注入实际需要的方法。底层的 stream_chat.Client
实现了所有这些方法,但是单独的模拟可以保持较小并且更容易理解。个人认为这并不值得开销。有很多开源的模拟生成器,尤其是 mock
和 mockgen
,还有从结构体生成接口的工具。
是否有一种方法可以将 gin.HandlerFunc(即 func(c *gin.Context))与创建 stream_chat.Client 的过程正确解耦?
你有几个选项,可以在这里找到:https://stackoverflow.com/questions/34046194/how-to-pass-arguments-to-router-handlers-in-golang-using-gin-web-framework/69338949
简而言之,我更喜欢的选项是为了更好的单元测试性:
- 将处理程序作为结构体的方法,并将依赖项作为该结构体的字段。
- 使用提供者模式,并在中间件中将提供者设置到 Gin 上下文中。
英文:
> Is maintaining an interface for stream_chat.Client the correct way to go?
If you have a non-interface dependency and you wish to unit test handlers with that, then yes. You need to wrap stream_chat.Client
in an interface.
If the third-party struct has a lot of methods, you could split the interface in logical units and inject in each handler only those that are actually needed. The underlying stream_chat.Client
implements all of them, but the individual mocks can be kept small and easier to reason about. Personally, I don't think it's worth the overhead. There's plenty of open-source mock generators, above all mock
and mockgen
, and also tools that generate interfaces from structs.
> Is there a way to properly decouple the gin.HandlerFunc, i.e. func(c *gin.Context) from the creation of stream_chat.Client?
You have several options, which you can find here: https://stackoverflow.com/questions/34046194/how-to-pass-arguments-to-router-handlers-in-golang-using-gin-web-framework/69338949
In short, the options I prefer are due to better unit-testability are:
- make the handlers methods of a struct and your dependencies fields of this struct.
- use a provider pattern and set the provider into the Gin context in a middleware
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论