英文:
Unit testing http handlers in golang mocking dependencies
问题
目前,我正在尝试为Go处理程序建立单元测试的最佳实践。我需要模拟依赖项,但为此我必须能够访问/模拟这些依赖项。
有一些解决方案我不想考虑,比如全局变量/应用程序状态。或者将所有处理程序作为持有依赖项成员变量的结构体函数。
我对一种解决方案感到满意,即以以下方式注入处理程序所需的依赖项:
func helloHandler(db *DbService) http.HandlerFunc {
return func(w http.ResponseWriter, r *httpRequest) {
// 处理程序代码在这里
}
}
然后,我可以将此处理程序提供给路由:
http.HandleFunc("/hello", helloHander(myDbService))
我也可以轻松地对其进行测试:
helloHandler(myDbService)(req, respRecorder)
但是,当我使用gorilla mux(一开始我没有考虑到)时,我遇到了另一个问题。
Gorilla/mux添加了额外的语义,比如按方法(GET / POST)进行过滤,提供路径参数等。
因此,我应该需要测试生成的gorilla路由器。但是,这样一来,我就无法再注入我的模拟依赖项了。
当我获取路由器后,一切(依赖项)都已设置好了。
但是,我也不想在我的测试中重新构建路由器,因为要遵循DRY原则!
所以目前我还没有一个好的解决方案,既能方便地设置我的路由,又能够模拟依赖项。
有什么想法吗?
英文:
At the moment I try to establish best practices for unit testing go handlers.
I need to mock dependencies, but therefore I have to be able to access / mock these dependencies.
There I some solutions I do not want to consider like for example global variables / application state. Or having all handlers as functions of a struct holding the dependencies member variables.
I was kind of satisfied with a solution where I injected the needed dependencies of a handler in the following way:
<!-- language-all: go -->
func helloHandler(db *DbService) http.HandlerFunc {
return func(w http.ResponseWriter, r *httpRequest) {
// handler code goes here
}
}
Then I am able to provide this handler for routing:
http.HandleFunc("/hello", helloHander(myDbService))
I can also test it easily:
helloHandler(myDbService)(req, respRecorder)
But when I use for example gorilla mux (what I haven't considered from the beginning) I get another problem.
Gorilla/mux adds extra semantics like filtering by Method (GET / POST), providing path parameters etc.
So I should need to test the resulting gorilla router.
But then I am again not able to inject my mock dependencies anymore.
When I get the router back everything (dependencies) is already set up.
But I also don't want to rebuild my Router in my tests because of DRY!
So for now I don't really have a good solution for the problem of conveniently setting up my routes but also being able to mock the dependencies.
Any idea?
答案1
得分: 7
我个人将我的路由器功能更改为带有接收器的方法,这样我就可以创建模拟接收器:
生产代码:
func main() {
router := mux.NewRouter()
version.AddRouter(router, contextRoot)
someStruct := SomeStruct{SomeDependency{}}
router.HandleFunc(contextRoot, someStruct.HandleRequests).
Methods(http.MethodGet)
}
func (s SomeStruct) HandleRequests(writer http.ResponseWriter, reader *http.Request) {
// 处理请求的逻辑
}
func (s SomeDependency) SomeFunctionCalledFromHandler(...) SomeReturnStruct {
// 处理函数被处理器调用的逻辑
return SomeReturnStruct{}
}
单元测试:
type mockWriter struct {}
type someMockDependency struct {}
func Test_HandleRequest1(t *testing.T) {
someStructMockDeps := SomeStruct{someMockDependency{}}
someStructMockDeps.HandleRequests(mockWriter{}, &http.Request{URL: &url.URL{RawQuery:"http://dummy-query.com"}})
assert.Equal(...)
}
func (someMockDependency) SomeFunctionCalledFromHandler(...) SomeReturnStruct {
// 处理函数被处理器调用的逻辑
return SomeReturnStruct{}
}
集成测试:
func TestHandlerFuncIntg(t *testing.T) {
if testing.Short() {
println("skipping")
t.Skip()
}
req, err := http.NewRequest("GET", "/hello?param=value", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
someStructMockDeps := SomeStruct{someMockDependency{}}
handler := http.HandlerFunc(someStructMockDeps.HandleRequests)
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, "This is my result", rr.Body.String())
}
func (someMockDependency) SomeFunctionCalledFromHandler(...) SomeReturnStruct {
// 处理函数被处理器调用的逻辑
return SomeReturnStruct{}
}
SomeStruct
声明了依赖项(作为依赖注入),以便可以在测试中进行覆盖。
英文:
I personally changed my Router function into a method with a Receiver, so I can create Mock receivers:
###Production code:
func main(){
router := mux.NewRouter()
version.AddRouter(router, contextRoot)
someStruct := SomeStruct{SomeDependency{}}
router.HandleFunc(contextRoot, someStruct.HandleRequests).
Methods(http.MethodGet)
}
func (s SomeStruct) HandleRequests(writer http.ResponseWriter, reader *http.Request) {
...
}
func (s SomeDependency) SomeFunctionCalledFromHandler(...) SomeReturnStruct {
...
return SomeReturnStruct{}
}
###Unit test:
type mockWriter struct {}
type someMockDependency struct {}
func Test_HandleRequest1(t *testing.T) {
someStructMockDeps := SomeStruct{someMockDependency{}}
someStructMockDeps.HandleRequests(mockWriter{}, &http.Request{URL: &url.URL{RawQuery:"http://dummy-query.com"}});
assert.Equal(...)
}
func (someMockDependency) SomeFunctionCalledFromHandler(...) SomeReturnStruct {
...
return SomeReturnStruct{}
}
###Integration test:
func TestHandlerFuncIntg(t *testing.T) {
if testing.Short() {
println("skipping")
t.Skip()
}
req, err := http.NewRequest("GET", "/hello?param=value", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
someStructMockDeps := SomeStruct{someMockDependency{}}
handler := http.HandlerFunc(someStructMockDeps.HandleRequests)
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, "This is my result", rr.Body.String())
}
func (someMockDependency) SomeFunctionCalledFromHandler(...) SomeReturnStruct {
...
return SomeReturnStruct{}
}
SomeStruct
declares the dependencies (as Dependency Injection) so they can be overridden for tests.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论