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?
CodePudding user response:
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: How to pass arguments to router handlers in Golang using Gin web framework?
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