英文:
httptest ResponseRecorder keeps the old value
问题
我有一个需要测试的handlerAuthentication函数:
func handlerAuthentication(c *gin.Context) {
	session := Session.GetSession(c)
	var login Login
	err := c.BindJSON(&login)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	client, err := initClient(c, login)
	fmt.Println("Error: ", err)
	if err != nil {
		fmt.Println("There's an error!")
		c.JSON(http.StatusUnauthorized, gin.H{"error": ErrorWrongLogin})
		return
	}
	err = (*client).Logout(c)
	if err != nil {
		return
	}
	session.Set("username", login.Username)
	session.Set("password", login.Password)
	err = session.Save()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "an error occurred during the save of the session:" + err.Error()})
		return
	}
	c.JSON(http.StatusOK, "Connected")
}
为了测试它,我做了以下工作:
func TestHandlerAuthentication(t *testing.T) {
	UrlOdoo = "https://isi.nc"
	resp := httptest.NewRecorder()
	gin.SetMode(gin.TestMode)
	c, r := gin.CreateTestContext(resp)
	r.POST("/test", func(c *gin.Context) {
		handlerAuthentication(c)
	})
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	Odoo = OdooRPC{createMockOdooClient}
	client = mock_odoorpc.NewMockOdooClient(ctrl)
	client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), invalidUsername, invalidPassword).AnyTimes().Return(fmt.Errorf("invalid login"))
	client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), validUsername, validPassword).AnyTimes().Return(nil)
	client.EXPECT().Logout(gomock.Any()).AnyTimes().Return(nil)
	session = mock_session.NewMockSession(ctrl)
	Session = SessionGetter{createMockSession}
	session.EXPECT().Set("username", validUsername).AnyTimes().Return()
	session.EXPECT().Set("password", validPassword).AnyTimes().Return()
	session.EXPECT().Save().AnyTimes().Return(nil)
	for name, test := range map[string]struct {
		input Login
		want  int
	}{
		"valid login": {
			input: Login{
				Username: validUsername,
				Password: validPassword,
			},
			want: 200,
		},
		"invalid login": {
			input: Login{
				Username: invalidUsername,
				Password: invalidPassword,
			},
			want: 401,
		},
	} {
		t.Run(name, func(t *testing.T) {
			body, _ := json.Marshal(test.input)
			c.Request, _ = http.NewRequest(http.MethodPost, "/test", strings.NewReader(string(body)))
			r.ServeHTTP(resp, c.Request)
			assert.Equal(t, test.want, resp.Code)
			resp.Flush()
		})
	}
}
我面临的问题是,如果我逐个运行测试(valid login和invalid login),它们都会通过,但是当我同时运行这两个测试时,第二个测试会失败。
以下是同时执行这两个测试的示例:
=== RUN   TestHandlerAuthentication
=== RUN   TestHandlerAuthentication/valid_login
Error: <nil> //没有错误,所以resp.Code应该等于200
=== RUN   TestHandlerAuthentication/invalid_login
Error: invalid login //有错误,所以resp.Code应该等于401
There's an error!
main_test.go:394: 
Error Trace:    main_test.go:394
Error:          Not equal: 
expected: 401
actual  : 200
Test:           TestHandlerAuthentication/invalid_login
--- FAIL: TestHandlerAuthentication (0.00s)
--- PASS: TestHandlerAuthentication/valid_login (0.00s)
--- FAIL: TestHandlerAuthentication/invalid_login (0.00s)
Expected :401
Actual   :200
正如预期的那样,当登录无效时发生错误,但是resp.Code仍然是200。
如果我先运行invalid login测试,resp.Code仍然是401。
这是因为测试是并行执行的,而httptest的ResponseRecorder在并行中不起作用吗?
谢谢你的帮助。
英文:
I have a handlerAuthentication function that I need to test:
func handlerAuthentication(c *gin.Context) {
	session := Session.GetSession(c)
	var login Login
	err := c.BindJSON(&login)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	client, err := initClient(c, login)
	fmt.Println("Error: ",err)
	if err != nil {
		fmt.Println("There's an error !")
		c.JSON(http.StatusUnauthorized, gin.H{"error": ErrorWrongLogin})
		return
	}
	err = (*client).Logout(c)
	if err != nil {
		return
	}
	session.Set("username", login.Username)
	session.Set("password", login.Password)
	err = session.Save()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "an error occurred during the save of the session:" + err.Error()})
		return
	}
	c.JSON(http.StatusOK, "Connected")
}
To do so,I made this:
func TestHandlerAuthentication(t *testing.T) {
	UrlOdoo = "https://isi.nc"
	resp := httptest.NewRecorder()
	gin.SetMode(gin.TestMode)
	c, r := gin.CreateTestContext(resp)
	r.POST("/test", func(c *gin.Context) {
		handlerAuthentication(c)
	})
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	Odoo = OdooRPC{createMockOdooClient}
	client = mock_odoorpc.NewMockOdooClient(ctrl)
	client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), invalidUsername, invalidPassword).AnyTimes().Return(fmt.Errorf("invalid login"))
	client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), validUsername, validPassword).AnyTimes().Return(nil)
	client.EXPECT().Logout(gomock.Any()).AnyTimes().Return(nil)
	session = mock_session.NewMockSession(ctrl)
	Session = SessionGetter{createMockSession}
	session.EXPECT().Set("username", validUsername).AnyTimes().Return()
	session.EXPECT().Set("password", validPassword).AnyTimes().Return()
	session.EXPECT().Save().AnyTimes().Return(nil)
	for name, test := range map[string]struct {
		input   Login
		want    int
	}{
		"valid login": {
			input: Login{
				Username: validUsername,
				Password: validPassword,
			},
			want: 200,
		},
		"invalid login": {
			input: Login{
				Username: invalidUsername,
				Password: invalidPassword,
			},
			want: 401,
		},
	} {
		t.Run(name, func(t *testing.T) {
			body, _ := json.Marshal(test.input)
			c.Request, _ = http.NewRequest(http.MethodPost, "/test", strings.NewReader(string(body)))
			r.ServeHTTP(resp, c.Request)
			assert.Equal(t, test.want, resp.Code)
			resp.Flush()
		})
	}
}
The problem I'm facing is that if I do the tests one by (valid login and invalid login), they all pass, but when I do the two tests at the same time, the second test fails.
Here's an exemple of execution of the two tests at the same time:
=== RUN   TestHandlerAuthentication
=== RUN   TestHandlerAuthentication/valid_login
Error:  <nil> //No error, so resp.Code should be equal to 200
=== RUN   TestHandlerAuthentication/invalid_login
Error:  invalid login //Error, so resp.Code should be equal to 401
There's an error !
main_test.go:394: 
Error Trace:	main_test.go:394
Error:      	Not equal: 
expected: 401
actual  : 200
Test:       	TestHandlerAuthentication/invalid_login
--- FAIL: TestHandlerAuthentication (0.00s)
--- PASS: TestHandlerAuthentication/valid_login (0.00s)
--- FAIL: TestHandlerAuthentication/invalid_login (0.00s)
Expected :401
Actual   :200
As expected, an error occured when the login is invalid, but the resp.Code is still 200.
And if I do the invalid login test first, the resp.Code will still be 401.
Is it happening because the tests are parallelized and the httptest ResponseRecorder doesn't work in parallel ?
Thank you for your help.
答案1
得分: 0
谢谢leaf bebop。
我需要为每个测试初始化一个新的httptest.ResponseRecorder。为此,我将初始化移到了t.Run(name,func(t *testing.T)函数中:
func TestHandlerAuthentication(t *testing.T) {
	UrlOdoo = "https://isi.nc"
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	Odoo = OdooRPC{createMockOdooClient}
	client = mock_odoorpc.NewMockOdooClient(ctrl)
	client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), invalidUsername, invalidPassword).AnyTimes().Return(fmt.Errorf("invalid login"))
	client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), validUsername, validPassword).AnyTimes().Return(nil)
	client.EXPECT().Logout(gomock.Any()).AnyTimes().Return(nil)
	session = mock_session.NewMockSession(ctrl)
	Session = SessionGetter{createMockSession}
	session.EXPECT().Set("username", validUsername).AnyTimes().Return()
	session.EXPECT().Set("password", validPassword).AnyTimes().Return()
	session.EXPECT().Save().AnyTimes().Return(nil)
	for name, test := range map[string]struct {
		input Login
		want  int
	}{
		"valid login": {
			input: Login{
				Username: validUsername,
				Password: validPassword,
			},
			want: 200,
		},
		"invalid login": {
			input: Login{
				Username: invalidUsername,
				Password: invalidPassword,
			},
			want: 401,
		},
	} {
		t.Run(name, func(t *testing.T) {
			resp := httptest.NewRecorder()
			gin.SetMode(gin.TestMode)
			c, r := gin.CreateTestContext(resp)
			r.POST("/test", func(c *gin.Context) {
				handlerAuthentication(c)
			})
			body, _ := json.Marshal(test.input)
			c.Request, _ = http.NewRequest(http.MethodPost, "/test", strings.NewReader(string(body)))
			r.ServeHTTP(resp, c.Request)
			assert.Equal(t, test.want, resp.Code)
		})
	}
}
英文:
Thank you leaf bebop
I needed to initialize a new httptest.ResponseRecorder for each test.
To do so, I move the initialisation to the t.Run(name,func(t *testing.T) function:
func TestHandlerAuthentication(t *testing.T) {
	UrlOdoo = "https://isi.nc"
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	Odoo = OdooRPC{createMockOdooClient}
	client = mock_odoorpc.NewMockOdooClient(ctrl)
	client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), invalidUsername, invalidPassword).AnyTimes().Return(fmt.Errorf("invalid login"))
	client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), validUsername, validPassword).AnyTimes().Return(nil)
	client.EXPECT().Logout(gomock.Any()).AnyTimes().Return(nil)
	session = mock_session.NewMockSession(ctrl)
	Session = SessionGetter{createMockSession}
	session.EXPECT().Set("username", validUsername).AnyTimes().Return()
	session.EXPECT().Set("password", validPassword).AnyTimes().Return()
	session.EXPECT().Save().AnyTimes().Return(nil)
	for name, test := range map[string]struct {
		input Login
		want  int
	}{
		"valid login": {
			input: Login{
				Username: validUsername,
				Password: validPassword,
			},
			want: 200,
		},
		"invalid login": {
			input: Login{
				Username: invalidUsername,
				Password: invalidPassword,
			},
			want: 401,
		},
	} {
		t.Run(name, func(t *testing.T) {
			resp := httptest.NewRecorder()
			gin.SetMode(gin.TestMode)
			c, r := gin.CreateTestContext(resp)
			r.POST("/test", func(c *gin.Context) {
				handlerAuthentication(c)
			})
			body, _ := json.Marshal(test.input)
			c.Request, _ = http.NewRequest(http.MethodPost, "/test", strings.NewReader(string(body)))
			r.ServeHTTP(resp, c.Request)
			assert.Equal(t, test.want, resp.Code)
		})
	}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论