Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong status code returned when using httptest.NewRecorder #1120

Open
emiguens opened this issue Sep 28, 2017 · 12 comments
Open

Wrong status code returned when using httptest.NewRecorder #1120

emiguens opened this issue Sep 28, 2017 · 12 comments

Comments

@emiguens
Copy link

When testing a gin handler using a httptest.NewRecorder, when using (inside the handler) c.Status(404) to set the status code, and then bailing out of the request, then the status from the recorder is wrongly returned as 200.

On the other hand if we use c.JSON(404, gin.H{}) then the status code inside the recorder is correct.

The expected behavior is of course to always receive in the recorder the status that was set inside the handler.

Example code reproducing the bug bellow, both tests are expected to PASS but only the one using JSON does.

Having a main.go with the following code:

package main

import (
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	port := ":" + os.Getenv("PORT")

	r := gin.Default()
	r.GET("/fail1", failStatus)
	r.GET("/fail2", failJson)
	r.Run(port)
}

func failStatus(c *gin.Context) {
	c.Status(http.StatusNotFound)
}

func failJson(c *gin.Context) {
	c.JSON(http.StatusNotFound, gin.H{"error": true})
}

And a main_test.go:

package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
)

func TestFailStatus(t *testing.T) {
	gin.SetMode(gin.TestMode)

	rec := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(rec)
	c.Request, _ = http.NewRequest("GET", "/fail1", nil)

	failStatus(c)

	res := rec.Result()
	if res.StatusCode != http.StatusNotFound { // Should be 404
		t.Fatalf("Expecting status to be 404 got %v", res.StatusCode)
	}
}

func TestFailJson(t *testing.T) {
	gin.SetMode(gin.TestMode)

	rec := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(rec)
	c.Request, _ = http.NewRequest("GET", "/fail1", nil)

	failJson(c)

	res := rec.Result()
	if res.StatusCode != http.StatusNotFound { // Should be 404
		t.Fatalf("Expecting status to be 404 got %v", res.StatusCode)
	}
}
@easonlin404
Copy link
Contributor

Because testing handler didn't through gin.Engine(router). You can use the following example:

package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
)


func TestFailStatus(t *testing.T) {
	gin.SetMode(gin.TestMode)

	router := gin.New()

	res:=performRequest("GET","/fail1",router)

	if res.Code != http.StatusNotFound { // Should be 404
		t.Fatalf("Expecting status to be 404 got %v", res.Code)
	}
}

func TestFailJson(t *testing.T) {
	gin.SetMode(gin.TestMode)

	router := gin.New()

	res:=performRequest("GET","/fail2",router)

	if res.Code != http.StatusNotFound { // Should be 404
		t.Fatalf("Expecting status to be 404 got %v", res.Code)
	}
}

func performRequest(method, target string, router *gin.Engine) *httptest.ResponseRecorder {
	r := httptest.NewRequest(method, target, nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, r)
	return w
}

@emiguens
Copy link
Author

Hi @easonlin404, thanks for your answer.

Correct me if I'm wrong, but in this case I would be testing the handler and the engine and not only the handler.

In a real project I would have a struct that it's instantiated by injecting dependencies (when testing mocks) and then the a Handler that is called. What I want to test is the logic of my handler, and not the routing/engine/middlewares/handler combination (that would be another kind of test I think).

Either way, the thing that I consider a bug is that c.Status does not behave the same way as c.JSON or c.String in a testing context. The expected behavior would be for all methods of context that assign a status code on the response to work the same way and make the status visible on the httptest.NewRecorder instance.

@damianraffa
Copy link

I got the same issue error in testing context.

  • c.Status(201) cannot set the status
  • c.JSON(201,...) setting status ok

@jbpratt
Copy link

jbpratt commented May 29, 2019

Bumping as error is still reproducible and discussion has been silent. 😄 Any update on the behavior of c.JSON vs c.Status not performing consistently?

@lsattem
Copy link

lsattem commented Oct 8, 2019

This bug is still present. Setting c.Status(204) does not result in the responserecorder's code being updated. My workaround at the moment is just getting the status from c.Writer.Status() in tests.

@marcoaltiericoupa
Copy link

marcoaltiericoupa commented Feb 27, 2020

This bug is still present. Setting c.Status(204) does not result in the responserecorder's code being updated. My workaround at the moment is just getting the status from c.Writer.Status() in tests.

This, I think, is the best workaround available.

@Alechan
Copy link

Alechan commented Jan 21, 2021

Another option is to "force" a flush in the tests with a ginContext.Writer.WriteHeaderNow(). In the example from the first comment:

func TestFailStatus(t *testing.T) {
	gin.SetMode(gin.TestMode)

	rec := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(rec)
	c.Request, _ = http.NewRequest("GET", "/fail1", nil)

	failStatus(c)

	//NEW LINE: Force flush
	c.Writer.WriteHeaderNow()

	res := rec.Result()
	if res.StatusCode != http.StatusNotFound { // Should be 404
		t.Fatalf("Expecting status to be 404 got %v", res.StatusCode)
	}
}

@khurram-s
Copy link

khurram-s commented Sep 21, 2021

Do we have a plan to solve this ?
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Status(204)

w.Result().StatusCode will always return 200.

Thank you for workaround propose above to use c.Writer.Status()

@janjulienn-kumu
Copy link

After 5 years since the issue opened, the same issue still occurs.

Using workaround mentioned above. (c.Writer.Status())

@j-h-a
Copy link

j-h-a commented Apr 25, 2023

Still having this problem... did a workaround in the tests like this:

if ctx.Writer.Status() != res.Code {
	ctx.Writer.WriteHeaderNow()
}

dnsge added a commit to dnsge/gin-standard-api that referenced this issue May 3, 2023
@LombardiDaniel
Copy link

Issue still happing in 2024...

@LombardiDaniel
Copy link

I think I found the [solution] (https://stackoverflow.com/questions/77951149/error-on-testing-gingko-and-gomega-for-gingonic-w-code-not-working/77957924#77957924)

It seems that the httptest.ResponseRecorder can only be written once. In my test-suite, the first test is expected to return 200 OK, so it sets that value in the response recorder.

This seemed to fix the issue:

req, err := http.NewRequest(http.MethodPut, "/v1/create-user", bytes.NewReader(body))
Expect(err).NotTo(HaveOccurred())
req.Header.Set("Content-Type", "application/json")

w := httptest.NewRecorder()  // <- creates a new response recorder

router.ServeHTTP(w, req)
Ω(w.Code).Should(Equal(http.StatusBadGateway))

This way the test runs successfully.

Note that i am NOT using gin.CreateTestContext().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests