代码中用到了firebase的Authentication,在测试的过程中,需要从Request header里面读取IdToken, 而这个是第三方签发的,无法自己模拟(自己做出来的都是CustomeToken不是一个东西,token的header是不一样的)。现实中也不可能每次跑测试的时候就启动service去搞一个token复制粘贴,就想到了mock。
Golang自己有mock的标准库,但都需要安装CLI,跑一个命令自动生成一些代码。感觉特别麻烦,研究了一下还是决定用Golang interface的强大功能来手动mock。
基本思路是把server的authentication功能分离出来,做成一个interface,在实际使用中使用真正的firebase auth,测试的时候,传入一个假的。
一开始server差不多是这样的。所有的方法都是Server的方法,耦合度很高。
type Server sturct {
config Config
routes *chi.Mux
db *postgres.db
}
// Authentication
func (s *Server) Authenticate (r *http.Request) *auth.Token, error {
// init firebase app, get client, verifyToken, not runable code
tokenString = strings.Split(r.Header.Get("Authorization"), "Bearer ")[1]
token, err := client.VerifyIDToken(ctx, tokenString)
// check err and return decoded token as IdToken Struct
return token, err
}
首先,给Server加一个field, 同时定义AuthMethod接口
type Server struct {
config Config
db *postgres.DB
router *chi.Mux
authentication AuthMethod
}
type AuthMethod interface {
Authenticate(*http.Request) (*auth.Token, error)
}
type FirebaseAuth struct{}
把原有的Authenticate方法改成新的类FirebaseAuth的方法:
func (s *FirebaseAuth) Authenticate (r *http.Request) *auth.Token, error {...}
在测试文件中定义一个mock类,并实现Authenticate方法:
type MockAuth struct{}
func (m *MockAuth) Authenticate(r *http.Request) (*auth.Token, error) {
fmt.Println("Mock token authentication")
return &auth.Token{UID: "test-firebase-uid"}, nil
}
启动一个Server实例时,传入&FirebaseAuth{} 或者&MockAuth{} 即可。
进一步思考,在实际定义struct的时候,层级越高(比如Server),就应该把功能全部分解开,都定义成接口,增加测试的灵活性,降低更改难度。
References
https://medium.com/safetycultureengineering/flexible-mocking-for-testing-in-go-f952869e34f5
https://stackoverflow.com/questions/42839517/how-to-mock-only-specific-method-in-golang