mux.Vars无法从httpTest请求中检索变量。

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

mux.Vars is not able to retrieve var from httpTest request

问题

我正在为将来的项目编写一个简单的 REST 脚手架,目前我正在为我的控制器编写一些测试。我正在尝试通过 /todo/{id} 来获取一个待办事项,以下是处理程序的代码:

func (t TodoController) GetById(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	id, err := strconv.Atoi(params["id"])

	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	todo, err := t.todoService.GetById(id)

	if err != nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	helpers.SendResponse(http.StatusOK, todo, w)
}

这是针对该控制器的测试代码:

var (
	todoController TodoController
	recorder       *httptest.ResponseRecorder
	todos          []models.Todo
)

func setup() {
	todoController = *NewTodoController(services.NewTodoService())
	recorder = httptest.NewRecorder()
	todos = []models.Todo{
		{
			ID:        0,
			Todo:      "购买牛奶",
			Completed: false,
		},
		{
			ID:        1,
			Todo:      "购买奶酪",
			Completed: false,
		},
		{
			ID:        2,
			Todo:      "购买鸡蛋",
			Completed: false,
		},
	}
}


func TestGetById(t *testing.T)  {
	// 准备
	setup()
	request := httptest.NewRequest(http.MethodGet, "/todo/1", nil)
	var response models.Todo

	// 执行
	todoController.GetById(recorder, request)
	result := recorder.Result()
	defer result.Body.Close()

	data, err := ioutil.ReadAll(result.Body)
	err = json.Unmarshal(data, &response)

	// 断言
	if err != nil {
		t.Errorf("期望的错误为 nil,但得到了 %v", err)
	}

	assert.Equal(t, result.StatusCode, http.StatusOK, "响应应该是 200 OK")
	assert.Equal(t, response, todos[1], "响应与预期结果不匹配")
}

看起来当发送请求到 /todo/1 时,mux 无法获取到 Id,因此返回了一个 BadRequest 错误。

这是该存储库的链接:https://github.com/Je12emy/rest_boiler

英文:

I'm writting a simple rest boiler plate for future projects, I'm currently working on some tests for my controller, I'm trying to retrieve a todo by it's Id at /todo/{id}, here's the handler.

func (t TodoController) GetById(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	id, err := strconv.Atoi(params["id"])

	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	todo, err := t.todoService.GetById(id)

	if err != nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	helpers.SendResponse(http.StatusOK, todo, w)
}

And here's the test for this controller.

var (
	todoController TodoController
	recorder       *httptest.ResponseRecorder
	todos          []models.Todo
)

func setup() {
	todoController = *NewTodoController(services.NewTodoService())
	recorder = httptest.NewRecorder()
	todos = []models.Todo{
		{
			ID:        0,
			Todo:      "Buy milk",
			Completed: false,
		},
		{
			ID:        1,
			Todo:      "Buy cheese",
			Completed: false,
		},
		{
			ID:        2,
			Todo:      "Buy eggs",
			Completed: false,
		},
	}
}


func TestGetById(t *testing.T)  {
	// Arrange
	setup()
	request := httptest.NewRequest(http.MethodGet, "/todo/1", nil)
	var response models.Todo

	// Act
	todoController.GetById(recorder, request)
	result := recorder.Result()
	defer result.Body.Close()

	data, err := ioutil.ReadAll(result.Body)
	err = json.Unmarshal(data, &response)

	// Assert
	if err != nil {
		t.Errorf("Expected error to be nil but got %v", err)
	}

	assert.Equal(t, result.StatusCode, http.StatusOK, "Response should have been 200 Ok")
	assert.Equal(t, response, todos[1], "Response did not match the expected result")
}

It looks like when sending a request to /todo/1 mux is not able to retrieve the Id, so it end up returning a BadRequest error.

Here's a link to this repo: https://github.com/Je12emy/rest_boiler

答案1

得分: 1

Mux提供了两种注入请求参数的方法 - SetURLVars助手和使用mux.Router包装器而不是直接调用处理程序。

SetURLVars正是你想要的 - 将本来无法访问的参数注入到HTTP请求中。

这种方法很简单,但有一个问题。使用的URL和注入的参数不同步。

没有禁止开发人员编写这样的代码:

// 我们在请求路径中使用2
request := httptest.NewRequest(http.MethodGet, "/todo/2", nil)
// 我们在请求参数中使用1
request = SetURLVars(request, map[string]string{"id":"1"})

这不是很干净的测试实践。

我们没有测试路由中的变量名是否正确。我们可以在路由器中使用item_id而不是id,测试不会捕捉到这个错误。

不仅如此,我们还可以在路由器定义中使用错误的路径,并映射到不同的处理程序。如果我们犯了这个错误,客户端可能会删除order而不是todo项目。

如果我们在测试中使用我们的生产路由器,这个问题可以解决。

假设这是我们的生产代码:

func InitRouter(t TodoController) http.handler
    r := mux.NewRouter()
    r.HandleFunc("/todo/{id}", t.GetById).Methods(http.MethodGet)
    return r

在测试中,我们可以通过在InitRouter函数中创建的路由器来测试GetById

func TestGetById(t *testing.T)  {
    // 准备
    setup()
    request := httptest.NewRequest(http.MethodGet, "/todo/1", nil)
    var response models.Todo
    // 添加准备
    sut := InitRouter(todoController)


    // 执行
    // 更改执行 - 通过生产路由器调用GetById
    sut.ServeHTTP(recorder, request)
    // 之后没有更改
    result := recorder.Result()
    defer result.Body.Close()

    ...
}


英文:

Mux provides two ways to inject request params - SetURLVars helper and using mux.Router wrapper instead of direct handler call.

SetURLVars does exactly what you want - injects otherwise not accessible parameters into HTTP request.

This method is simple, but has one issue though. Used URL and injected parameters are out of sync.

None forbids developers to write this code:

// we use 2 in request path
request := httptest.NewRequest(http.MethodGet, "/todo/2", nil)
// and we use 1 in request param
request = SetURLVars(request, map[string]string{"id":"1"})

This is not very clean testing practice.

We do not test if our var names in routing is correct. We can use item_id in router instead of id and test does not catch that.

Not only that, but we can use wrong path in router definition and map different handler. Client can delete order instead of todo item if we make that mistake.

That could be solved if we use our production Router in test.

Assume it is our production code:

func InitRouter(t TodoController) http.handler
    r := mux.NewRouter()
    r.HandleFunc("/todo/{id}", t.GetById).Methods(http.MethodGet)
    return r

In test, we can test GetById through router we created in InitRouter function:

func TestGetById(t *testing.T)  {
    // Arrange
    setup()
    request := httptest.NewRequest(http.MethodGet, "/todo/1", nil)
    var response models.Todo
    // added setup
    sut := InitRouter(todoController)


    // Act
    // changed act - calling GetById through production router
    sut.ServeHTTP(recorder, request)
    // no chnages after that
    result := recorder.Result()
    defer result.Body.Close()

    ...
}


huangapple
  • 本文由 发表于 2022年5月31日 01:31:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/72438100.html
匿名

发表评论

匿名网友

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

确定