How to mock a third-party struct with many methods and perform unit tests on endpoints that depend on the third-party struct?

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

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.UpsertUserchatClient.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中使用的每个方法似乎相当繁琐,所以我想知道在这种情况下应该怎么做?

  1. 维护一个stream_chat.Client的接口是正确的做法吗?
  2. 不太相关:是否有一种方法可以将gin.HandlerFunc(即func(c *gin.Context))与创建stream_chat.Client正确解耦?
  3. 更不相关:是创建一个单例的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?

  1. Is maintaining an interface for stream_chat.Client the correct way to go?
  2. Less relevant: Is there a way to properly decouple the gin.HandlerFunc, i.e. func(c *gin.Context) from the creation of stream_chat.Client?
  3. 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 实现了所有这些方法,但是单独的模拟可以保持较小并且更容易理解。个人认为这并不值得开销。有很多开源的模拟生成器,尤其是 mockmockgen,还有从结构体生成接口的工具。

是否有一种方法可以将 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

简而言之,我更喜欢的选项是为了更好的单元测试性:

  1. 将处理程序作为结构体的方法,并将依赖项作为该结构体的字段。
  2. 使用提供者模式,并在中间件中将提供者设置到 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:

  1. make the handlers methods of a struct and your dependencies fields of this struct.
  2. use a provider pattern and set the provider into the Gin context in a middleware

huangapple
  • 本文由 发表于 2022年3月10日 23:35:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/71426806.html
匿名

发表评论

匿名网友

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

确定