Skip to content

Commit 46c144e

Browse files
author
Chris Stockton
committed
fix: improve test readability
1 parent ca67be0 commit 46c144e

File tree

1 file changed

+118
-106
lines changed

1 file changed

+118
-106
lines changed

internal/e2e/e2eapi/e2eapi_test.go

Lines changed: 118 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package e2eapi
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"io"
78
"net/http"
89
"net/http/httptest"
@@ -21,42 +22,34 @@ func TestInstance(t *testing.T) {
2122
ctx, cancel := context.WithTimeout(context.Background(), time.Second*4)
2223
defer cancel()
2324

24-
globalCfg := e2e.Must(e2e.Config())
25-
inst, err := New(globalCfg)
26-
if err != nil {
27-
t.Fatalf("exp nil err; got %v", err)
28-
}
29-
defer inst.Close()
30-
31-
{
32-
email := "e2etesthooks_" + uuid.Must(uuid.NewV4()).String() + "@localhost"
33-
req := &api.SignupParams{
34-
Email: email,
35-
Password: "password",
36-
}
37-
res := new(models.User)
38-
err := Do(ctx, http.MethodPost, inst.APIServer.URL+"/signup", req, res)
39-
if err != nil {
40-
t.Fatalf("exp nil err; got %v", err)
41-
}
42-
require.Equal(t, email, res.Email.String())
43-
}
44-
}
25+
t.Run("New", func(t *testing.T) {
26+
t.Run("Success", func(t *testing.T) {
27+
globalCfg := e2e.Must(e2e.Config())
28+
inst, err := New(globalCfg)
29+
require.NoError(t, err)
30+
defer inst.Close()
31+
32+
email := "e2etesthooks_" + uuid.Must(uuid.NewV4()).String() + "@localhost"
33+
req := &api.SignupParams{
34+
Email: email,
35+
Password: "password",
36+
}
37+
res := new(models.User)
38+
err = Do(ctx, http.MethodPost, inst.APIServer.URL+"/signup", req, res)
39+
require.NoError(t, err)
40+
require.Equal(t, email, res.Email.String())
41+
})
4542

46-
func TestNew(t *testing.T) {
47-
{
48-
globalCfg := e2e.Must(e2e.Config())
49-
globalCfg.DB.Driver = ""
50-
globalCfg.DB.URL = "invalid"
43+
t.Run("Failure", func(t *testing.T) {
44+
globalCfg := e2e.Must(e2e.Config())
45+
globalCfg.DB.Driver = ""
46+
globalCfg.DB.URL = "invalid"
5147

52-
inst, err := New(globalCfg)
53-
if err == nil {
54-
t.Fatal("exp non-nil err")
55-
}
56-
if inst != nil {
57-
t.Fatal("exp nil *Instance")
58-
}
59-
}
48+
inst, err := New(globalCfg)
49+
require.Error(t, err)
50+
require.Nil(t, inst)
51+
})
52+
})
6053
}
6154

6255
func TestDo(t *testing.T) {
@@ -65,63 +58,57 @@ func TestDo(t *testing.T) {
6558

6659
globalCfg := e2e.Must(e2e.Config())
6760
inst, err := New(globalCfg)
68-
if err != nil {
69-
t.Fatalf("exp nil err; got %v", err)
70-
}
61+
require.NoError(t, err)
7162
defer inst.Close()
7263

73-
{
64+
// Covers calls to Do with a `req` param type which can't marshaled
65+
t.Run("InvalidRequestType", func(t *testing.T) {
7466
req := make(chan string)
7567
err := Do(ctx, http.MethodPost, "http://localhost", &req, nil)
76-
if err == nil {
77-
t.Fatal("exp non-nil err")
78-
}
68+
require.Error(t, err)
7969
require.ErrorContains(t, err, "json: unsupported type: chan string")
80-
}
70+
})
8171

82-
{
83-
res := make(chan string)
84-
err := Do(ctx, http.MethodGet, inst.APIServer.URL+"/user", nil, &res)
85-
if err == nil {
86-
t.Fatal("exp non-nil err")
87-
}
88-
require.ErrorContains(t, err, "401: This endpoint requires a Bearer token")
89-
}
90-
91-
{
72+
// Covers calls to Do with a `res` param type which can't marshaled
73+
t.Run("InvalidResponseType", func(t *testing.T) {
9274
res := make(chan string)
9375
err := Do(ctx, http.MethodGet, inst.APIServer.URL+"/settings", nil, &res)
94-
if err == nil {
95-
t.Fatal("exp non-nil err")
96-
}
76+
require.Error(t, err)
9777
require.ErrorContains(t, err, "json: cannot unmarshal object into Go value of type chan string")
98-
}
78+
})
79+
80+
// Covers status code >= 400 error handling switch statement
81+
t.Run("api.HTTPErrorResponse_to_apierrors.HTTPError", func(t *testing.T) {
82+
res := make(chan string)
83+
err := Do(ctx, http.MethodGet, inst.APIServer.URL+"/user", nil, &res)
84+
require.Error(t, err)
85+
require.ErrorContains(t, err, "401: This endpoint requires a Bearer token")
86+
})
9987

100-
{
88+
// Covers http.NewRequestWithContext
89+
t.Run("InvalidHTTPMethod", func(t *testing.T) {
10190
err := Do(ctx, "\x01", "http://localhost", nil, nil)
102-
if err == nil {
103-
t.Fatal("exp non-nil err")
104-
}
91+
require.Error(t, err)
10592
require.ErrorContains(t, err, "net/http: invalid method")
106-
}
93+
})
10794

108-
{
95+
// Covers status code >= 400 error handling switch statement json.Unmarshal
96+
// by hitting the default error handler that returns html
97+
t.Run("InvalidResponse", func(t *testing.T) {
10998
err := Do(ctx, http.MethodGet, inst.APIServer.URL+"/404", nil, nil)
110-
if err == nil {
111-
t.Fatal("exp non-nil err")
112-
}
99+
require.Error(t, err)
113100
require.ErrorContains(t, err, "invalid character")
114-
}
101+
})
115102

116-
{
103+
// Covers defaultClient.Do failure
104+
t.Run("InvalidURL", func(t *testing.T) {
117105
err := Do(ctx, http.MethodPost, "invalid", nil, nil)
118-
if err == nil {
119-
t.Fatal("exp non-nil err")
120-
}
106+
require.Error(t, err)
121107
require.ErrorContains(t, err, "unsupported protocol")
122-
}
108+
})
123109

124-
func() {
110+
// Covers http.StatusNoContent handling
111+
t.Run("InvalidRequestType", func(t *testing.T) {
125112
hr := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
126113
w.WriteHeader(http.StatusNoContent)
127114
})
@@ -130,46 +117,71 @@ func TestDo(t *testing.T) {
130117
defer ts.Close()
131118

132119
err := Do(ctx, http.MethodPost, ts.URL, nil, nil)
133-
if err != nil {
134-
t.Fatalf("exp nil err; got %v", err)
135-
}
136-
}()
137-
138-
for _, statusCode := range []int{http.StatusBadRequest, http.StatusOK} {
139-
func() {
140-
sentinel := errors.New("sentinel")
141-
rtFn := roundTripperFunc(func(req *http.Request) (*http.Response, error) {
142-
res, err := http.DefaultClient.Do(req)
143-
if err != nil {
144-
return nil, err
145-
}
146-
res.Body = io.NopCloser(iotest.ErrReader(sentinel))
147-
return res, nil
148-
})
149-
150-
prev := defaultClient
151-
defer func() {
152-
defaultClient = prev
153-
}()
154-
defaultClient = new(http.Client)
155-
defaultClient.Transport = rtFn
120+
require.NoError(t, err)
121+
})
122+
123+
// Covers IO errors
124+
t.Run("IOError", func(t *testing.T) {
125+
126+
for _, statusCode := range []int{http.StatusBadRequest, http.StatusOK} {
127+
128+
// Covers IO errors for the sc >= 400 and default status code
129+
// handling in the switch statement within do.
130+
testName := fmt.Sprintf("Status=%v", http.StatusText(statusCode))
131+
t.Run(testName, func(t *testing.T) {
132+
133+
// We assign a sentinel error to ensure propagation.
134+
sentinel := errors.New("sentinel")
135+
136+
// This implementation of the http.RoundTripper is a way to
137+
// cover the io.ReadAll(io.LimitReader(...)) lines in the switch
138+
// statements inside do().
139+
rtFn := roundTripperFunc(func(req *http.Request) (*http.Response, error) {
140+
141+
// Call the default http.RoundTripper implementation provided
142+
// by the http.Default client to build a valid http.Response.
143+
res, err := http.DefaultClient.Do(req)
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
// Wrap the res.Body in an io.ErrReader using our sentinel
149+
// error. This causes the first call to read the response
150+
// body to return our sentinel error.
151+
res.Body = io.NopCloser(iotest.ErrReader(sentinel))
152+
return res, nil
153+
})
154+
155+
// We need to swap the defaultClient with a new client which has
156+
// the (*Client).Transport set to our http.RoundTripper above.
157+
prev := defaultClient
158+
defer func() {
159+
defaultClient = prev
160+
}()
161+
defaultClient = new(http.Client)
162+
defaultClient.Transport = rtFn
163+
164+
hr := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
165+
w.WriteHeader(statusCode)
166+
})
167+
168+
ts := httptest.NewServer(hr)
169+
defer ts.Close()
170+
171+
// We send the request and expect back our sentinel error.
172+
err := Do(ctx, http.MethodPost, ts.URL, nil, nil)
173+
require.Error(t, err)
174+
require.Equal(t, sentinel, err)
156175

157-
hr := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
158-
w.WriteHeader(statusCode)
159176
})
160-
161-
ts := httptest.NewServer(hr)
162-
defer ts.Close()
163-
164-
err := Do(ctx, http.MethodPost, ts.URL, nil, nil)
165-
require.Error(t, err)
166-
require.Equal(t, sentinel, err)
167-
}()
168-
}
177+
}
178+
})
169179
}
170180

181+
// roundTripperFunc is like http.HandlerFunc for a http.RoundTripper
171182
type roundTripperFunc func(*http.Request) (*http.Response, error)
172183

184+
// RoundTrip implements http.RoundTripper by calling itself.
173185
func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
174186
return f(req)
175187
}

0 commit comments

Comments
 (0)