Skip to content

Commit ff5c42a

Browse files
committed
go-require: require-error: support HTTP handlers
1 parent e36c534 commit ff5c42a

17 files changed

+418
-97
lines changed

README.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ $ testifylint --enable-all --disable=empty,error-is-as ./...
6868
# Checkers configuration.
6969
$ testifylint --bool-compare.ignore-custom-types ./...
7070
$ testifylint --expected-actual.pattern=^wanted$ ./...
71+
$ testifylint --go-require.ignore-http-handlers ./...
7172
$ testifylint --require-error.fn-pattern="^(Errorf?|NoErrorf?)$" ./...
7273
$ testifylint --suite-extra-assert-call.mode=require ./...
7374
```
@@ -336,7 +337,7 @@ go func() {
336337
conn, err = lis.Accept()
337338
require.NoError(t, err) ❌
338339

339-
if assert.Error(err) {
340+
if assert.Error(err) {
340341
assert.FailNow(t, msg) ❌
341342
}
342343
}()
@@ -481,11 +482,11 @@ You can set `--require-error.fn-pattern` flag to limit the checking to certain c
481482
For example, `--require-error.fn-pattern="^(Errorf?|NoErrorf?)$"` will only check `Error`, `Errorf`, `NoError`, and `NoErrorf`.
482483

483484
Also, to minimize false positives, `require-error` ignores:
484-
- assertion in the `if` condition;
485-
- assertion in the bool expression;
485+
- assertions in the `if` condition;
486+
- assertions in the bool expression;
486487
- the entire `if-else[-if]` block, if there is an assertion in any `if` condition;
487488
- the last assertion in the block, if there are no methods/functions calls after it;
488-
- assertions in an explicit goroutine;
489+
- assertions in an explicit goroutine (including `http.Handler`);
489490
- assertions in an explicit testing cleanup function or suite teardown methods;
490491
- sequence of `NoError` assertions.
491492

analyzer/analyzer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const (
1919
url = "https://github.com/antonboom/" + name
2020
)
2121

22-
// New returns new instance of testifylint analyzer.
22+
// New returns a new instance of testifylint analyzer.
2323
func New() *analysis.Analyzer {
2424
cfg := config.NewDefault()
2525

analyzer/analyzer_test.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,15 @@ func TestTestifyLint(t *testing.T) {
5151
},
5252
{dir: "ginkgo"},
5353
{
54-
dir: "go-require-issue66",
54+
dir: "go-require-ignore-http-handlers",
55+
flags: map[string]string{
56+
"disable-all": "true",
57+
"enable": checkers.NewGoRequire().Name(),
58+
"go-require.ignore-http-handlers": "true",
59+
},
60+
},
61+
{
62+
dir: "go-require-issue66-issue73",
5563
flags: map[string]string{"enable-all": "true"},
5664
},
5765
{

analyzer/checkers_factory.go

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.Advan
5555
case *checkers.ExpectedActual:
5656
c.SetExpVarPattern(cfg.ExpectedActual.ExpVarPattern.Regexp)
5757

58+
case *checkers.GoRequire:
59+
c.SetIgnoreHTTPHandlers(cfg.GoRequire.IgnoreHTTPHandlers)
60+
5861
case *checkers.RequireError:
5962
c.SetFnPattern(cfg.RequireError.FnPattern.Regexp)
6063

analyzer/checkers_factory_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,20 @@ func Test_newCheckers(t *testing.T) {
166166
},
167167
expAdvanced: []checkers.AdvancedChecker{},
168168
},
169+
{
170+
name: "go-require ignore http handlers",
171+
cfg: config.Config{
172+
DisableAll: true,
173+
EnabledCheckers: config.KnownCheckersValue{checkers.NewGoRequire().Name()},
174+
GoRequire: config.GoRequireConfig{
175+
IgnoreHTTPHandlers: true,
176+
},
177+
},
178+
expRegular: []checkers.RegularChecker{},
179+
expAdvanced: []checkers.AdvancedChecker{
180+
checkers.NewGoRequire().SetIgnoreHTTPHandlers(true),
181+
},
182+
},
169183
{
170184
name: "require-equal fn pattern defined",
171185
cfg: config.Config{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package gorequireignorehttphandlers_test
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestServer_Require(t *testing.T) {
16+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
17+
file, err := os.Open("some file.json")
18+
require.NoError(t, err)
19+
20+
data, err := io.ReadAll(file)
21+
require.NoError(t, err)
22+
23+
w.Header().Set("Content-Type", "application/json")
24+
_, err = w.Write(data)
25+
if !assert.NoError(t, err) {
26+
assert.FailNow(t, err.Error())
27+
}
28+
}))
29+
defer ts.Close()
30+
31+
client := ts.Client()
32+
client.Timeout = 10 * time.Second
33+
34+
req, err := http.NewRequest("GET", ts.URL+"/require", nil)
35+
require.NoError(t, err)
36+
37+
statusCode := make(chan int)
38+
go func() {
39+
resp, err := client.Do(req)
40+
require.NoError(t, err) // want "go-require: require must only be used in the goroutine running the test function"
41+
defer func() {
42+
require.NoError(t, resp.Body.Close()) // want "go-require: require must only be used in the goroutine running the test function"
43+
}()
44+
statusCode <- resp.StatusCode
45+
}()
46+
47+
require.Equal(t, http.StatusOK, <-statusCode)
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package gorequireissue66issue73_test
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
"github.com/stretchr/testify/suite"
14+
)
15+
16+
// NOTE(a.telyshev): Neither `assert` nor `require` is the best way to test an HTTP handler:
17+
// it leads to redundant stack traces (up to runtime assembler), as well as undefined behaviour (in `require` case).
18+
// Use HTTP mechanisms (status code, headers, response data) and place assertions in the main test function.
19+
20+
func TestServer_Assert(t *testing.T) {
21+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
22+
file, err := os.Open("some file.json")
23+
if !assert.NoError(t, err) {
24+
return
25+
}
26+
27+
data, err := io.ReadAll(file)
28+
if !assert.NoError(t, err) {
29+
return
30+
}
31+
32+
w.Header().Set("Content-Type", "application/json")
33+
_, err = w.Write(data)
34+
assert.NoError(t, err)
35+
}))
36+
defer ts.Close()
37+
38+
client := ts.Client()
39+
40+
req, err := http.NewRequest("GET", ts.URL+"/assert", nil)
41+
assert.NoError(t, err) // want "require-error: for error assertions use require"
42+
43+
resp, err := client.Do(req)
44+
require.NoError(t, err)
45+
defer func() {
46+
assert.NoError(t, resp.Body.Close())
47+
}()
48+
49+
assert.Equal(t, http.StatusOK, resp.StatusCode)
50+
}
51+
52+
func TestServer_Require(t *testing.T) {
53+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
54+
file, err := os.Open("some file.json")
55+
require.NoError(t, err) // want "go-require: do not use require in http handlers"
56+
57+
data, err := io.ReadAll(file)
58+
require.NoError(t, err) // want "go-require: do not use require in http handlers"
59+
60+
w.Header().Set("Content-Type", "application/json")
61+
_, err = w.Write(data)
62+
if !assert.NoError(t, err) {
63+
assert.FailNow(t, err.Error()) // want "go-require: do not use assert\\.FailNow in http handlers"
64+
}
65+
}))
66+
defer ts.Close()
67+
68+
client := ts.Client()
69+
client.Timeout = 10 * time.Second
70+
71+
req, err := http.NewRequest("GET", ts.URL+"/require", nil)
72+
require.NoError(t, err)
73+
74+
resp, err := client.Do(req)
75+
require.NoError(t, err)
76+
defer func() {
77+
require.NoError(t, resp.Body.Close())
78+
}()
79+
80+
require.Equal(t, http.StatusOK, resp.StatusCode)
81+
}
82+
83+
type ServerSuite struct {
84+
suite.Suite
85+
}
86+
87+
func TestServerSuite(t *testing.T) {
88+
suite.Run(t, &ServerSuite{})
89+
}
90+
91+
func (s *ServerSuite) TestServer() {
92+
httptest.NewServer(http.HandlerFunc(s.handler))
93+
}
94+
95+
func (s *ServerSuite) handler(w http.ResponseWriter, _ *http.Request) {
96+
s.T().Helper()
97+
98+
file, err := os.Open("some file.json")
99+
s.Require().NoError(err) // want "go-require: do not use require in http handlers"
100+
101+
data, err := io.ReadAll(file)
102+
if !s.NoError(err) {
103+
return
104+
}
105+
106+
w.Header().Set("Content-Type", "application/json")
107+
_, err = w.Write(data)
108+
if !s.NoError(err) {
109+
s.FailNow(err.Error()) // want "go-require: do not use s\\.FailNow in http handlers"
110+
}
111+
}

analyzer/testdata/src/go-require-issue66/gorequire_requireerror_conflict_test.go

-50
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package requireerrorskiplogic
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/suite"
11+
)
12+
13+
func TestSomeServer(t *testing.T) {
14+
httptest.NewServer(http.HandlerFunc(func(hres http.ResponseWriter, hreq *http.Request) {
15+
var req MyRequest
16+
err := json.NewDecoder(hreq.Body).Decode(&req)
17+
assert.NoError(t, err)
18+
assert.Equal(t, "42", req.ID)
19+
}))
20+
21+
httptest.NewServer(http.HandlerFunc(func(hres http.ResponseWriter, hreq *http.Request) {
22+
var req MyRequest
23+
err := json.NewDecoder(hreq.Body).Decode(&req)
24+
if !assert.NoError(t, err) {
25+
return
26+
}
27+
assert.Equal(t, "42", req.ID)
28+
}))
29+
30+
httptest.NewServer(&handler{t: t})
31+
httptest.NewServer(http.HandlerFunc((&handler{t: t}).Handle))
32+
httptest.NewServer(handlerClosure(t))
33+
}
34+
35+
var _ http.Handler = (*handler)(nil)
36+
37+
type handler struct {
38+
t *testing.T
39+
}
40+
41+
func (h *handler) ServeHTTP(hres http.ResponseWriter, hreq *http.Request) {
42+
var req MyRequest
43+
err := json.NewDecoder(hreq.Body).Decode(&req)
44+
assert.NoError(h.t, err)
45+
assert.Equal(h.t, "42", req.ID)
46+
}
47+
48+
func (h *handler) Handle(hres http.ResponseWriter, hreq *http.Request) {
49+
var req MyRequest
50+
err := json.NewDecoder(hreq.Body).Decode(&req)
51+
assert.NoError(h.t, err)
52+
assert.Equal(h.t, "42", req.ID)
53+
}
54+
55+
func handlerClosure(t *testing.T) http.Handler {
56+
t.Helper()
57+
58+
return http.HandlerFunc(func(hres http.ResponseWriter, hreq *http.Request) {
59+
var req MyRequest
60+
err := json.NewDecoder(hreq.Body).Decode(&req)
61+
assert.NoError(t, err)
62+
assert.Equal(t, "42", req.ID)
63+
})
64+
}
65+
66+
type SomeServerSuite struct {
67+
suite.Suite
68+
}
69+
70+
func TestSomeServerSuite(t *testing.T) {
71+
suite.Run(t, &SomeServerSuite{})
72+
}
73+
74+
func (s *SomeServerSuite) TestServer() {
75+
httptest.NewServer(http.HandlerFunc(s.handler))
76+
}
77+
78+
func (s *SomeServerSuite) handler(hres http.ResponseWriter, hreq *http.Request) {
79+
var req MyRequest
80+
err := json.NewDecoder(hreq.Body).Decode(&req)
81+
s.Require().NoError(err)
82+
s.Equal("42", req.ID)
83+
}
84+
85+
type MyRequest struct {
86+
ID string
87+
}

0 commit comments

Comments
 (0)