Repository: gavv/httpexpect
Pull request #332 (2023-03-06 06:01:23)

function implementations for expect config
[#249](https://github.com/gavv/httpexpect/issues/249)

coveralls: 
[![Coverage Status](https://coveralls.io/builds/57550613/badge)](https://coveralls.io/builds/57550613)

Coverage: 94.954% (-0.1%) from 95.067% when pulling **8408baa5c481aedf79e4d960589642edcb2589c1 on L-block:expect-config-func-implementations** into **fc83351a75b1019d527f8e001d735f0bf180236f on gavv:master**.

gavv: #249
L-block: @gavv any ideas on testing the `LoggerFunc` and `ReporterFunc` as they are void methods? Typically, I would mimic what is happening with the `mockLogger` and `mockReporter` in `mocks_test.go` but that kinda defeats the purpose of implementation. The only thing I had in mind was something to this effect:
```go
func mockReporterFuncHelper(t *testing.T) func(message string, args ...interface{}) {
	return func(message string, args ...interface{}) {
		assert.NoError(t, nil)
	}
}
```
which....works, but isn't a very descriptive test.

Patch:
diff --git a/expect.go b/expect.go
index 44d73b9..7687786 100644
--- a/expect.go
+++ b/expect.go
@@ -285,6 +285,27 @@ type RequestFactory interface {
 	NewRequest(method, url string, body io.Reader) (*http.Request, error)
 }
 
+// RequestFactoryFunc is an adapter that allows a function
+// to be used as the RequestFactory
+//
+// Example:
+//
+//	e := httpexpect.WithConfig(httpexpect.Config{
+//		RequestFactory: httpextect.RequestFactoryFunc(
+//			func(method string, url string, body io.Reader) (*http.Request, error) {
+//				// factory code here
+//			}),
+//	})
+type RequestFactoryFunc func(
+	method string, url string, body io.Reader,
+) (*http.Request, error)
+
+func (f RequestFactoryFunc) NewRequest(
+	method string, url string, body io.Reader,
+) (*http.Request, error) {
+	return f(method, url, body)
+}
+
 // Client is used to send http.Request and receive http.Response.
 // http.Client implements this interface.
 //
@@ -303,6 +324,22 @@ type Client interface {
 	Do(*http.Request) (*http.Response, error)
 }
 
+// ClientFunc is an adapter that allows a function to be used as the Client
+//
+// Example:
+//
+//	e := httpexpect.WithConfig(httpexpect.Config{
+//		Client: httpextect.ClientFunc(
+//			func(req *http.Request) (*http.Response, error) {
+//				// client code here
+//			}),
+//	})
+type ClientFunc func(req *http.Request) (*http.Response, error)
+
+func (f ClientFunc) Do(req *http.Request) (*http.Response, error) {
+	return f(req)
+}
+
 // WebsocketDialer is used to establish websocket.Conn and receive http.Response
 // of handshake result.
 // websocket.Dialer implements this interface.
@@ -313,8 +350,7 @@ type Client interface {
 // Example:
 //
 //	e := httpexpect.WithConfig(httpexpect.Config{
-//	  BaseURL:         "http://example.com",
-//	  WebsocketDialer: httpexpect.NewWebsocketDialer(myHandler),
+//	    WebsocketDialer: httpexpect.NewWebsocketDialer(myHandler),
 //	})
 type WebsocketDialer interface {
 	// Dial establishes new Websocket connection and returns response
@@ -322,6 +358,27 @@ type WebsocketDialer interface {
 	Dial(url string, reqH http.Header) (*websocket.Conn, *http.Response, error)
 }
 
+// WebsocketDialerFunc is an adapter that allows a function
+// to be used as the WebsocketDialer
+//
+// Example:
+//
+//	e := httpexpect.WithConfig(httpexpect.Config{
+//		WebsocketDialer: httpextect.WebsocketDialerFunc(
+//			func(url string, reqH http.Header) (*websocket.Conn, *http.Response, error) {
+//				// dialer code here
+//			}),
+//	})
+type WebsocketDialerFunc func(
+	url string, reqH http.Header,
+) (*websocket.Conn, *http.Response, error)
+
+func (f WebsocketDialerFunc) Dial(
+	url string, reqH http.Header,
+) (*websocket.Conn, *http.Response, error) {
+	return f(url, reqH)
+}
+
 // Reporter is used to report failures.
 // *testing.T, FatalReporter, AssertReporter, RequireReporter implement it.
 type Reporter interface {
@@ -330,6 +387,22 @@ type Reporter interface {
 	Errorf(message string, args ...interface{})
 }
 
+// ReporterFunc is an adapter that allows a function to be used as the Reporter
+//
+// Example:
+//
+//	e := httpexpect.WithConfig(httpexpect.Config{
+//		Reporter: httpextect.ReporterFunc(
+//			func(message string, args ...interface{}) {
+//				// reporter code here
+//			}),
+//	})
+type ReporterFunc func(message string, args ...interface{})
+
+func (f ReporterFunc) Errorf(message string, args ...interface{}) {
+	f(message, args)
+}
+
 // Logger is used as output backend for Printer.
 // *testing.T implements this interface.
 type Logger interface {
@@ -337,6 +410,25 @@ type Logger interface {
 	Logf(fmt string, args ...interface{})
 }
 
+// LoggerFunc is an adapter that allows a function to be used as the Logger
+//
+// Example:
+//
+//	e := httpexpect.WithConfig(httpexpect.Config{
+//		Printers: []httpexpect.Printer{
+//			httpexpect.NewCompactPrinter(
+//				httpextect.LoggerFunc(
+//					func(fmt string, args ...interface{}) {
+//						// logger code here
+//					})),
+//		},
+//	})
+type LoggerFunc func(fmt string, args ...interface{})
+
+func (f LoggerFunc) Logf(fmt string, args ...interface{}) {
+	f(fmt, args)
+}
+
 // TestingTB is a subset of testing.TB interface used by httpexpect.
 // You can use *testing.T or pass custom implementation.
 type TestingTB interface {


Test patch:
diff --git a/expect_test.go b/expect_test.go
index 859440e..b5186ba 100644
--- a/expect_test.go
+++ b/expect_test.go
@@ -1,8 +1,12 @@
 package httpexpect
 
 import (
+	"errors"
+	"io"
+	"net/http"
 	"testing"
 
+	"github.com/gorilla/websocket"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -529,3 +533,109 @@ func TestExpect_Config(t *testing.T) {
 		})
 	})
 }
+
+func TestExpect_Adapters(t *testing.T) {
+	t.Run("RequestFactoryFunc", func(t *testing.T) {
+		called := false
+		factory := RequestFactoryFunc(func(
+			_ string, _ string, _ io.Reader,
+		) (*http.Request, error) {
+			called = true
+			return nil, nil
+		})
+
+		e := WithConfig(Config{
+			RequestFactory: factory,
+			Reporter:       newMockReporter(t),
+		})
+
+		e.Request("GET", "/")
+
+		assert.True(t, called)
+	})
+
+	t.Run("ClientFunc", func(t *testing.T) {
+		called := false
+		client := ClientFunc(func(_ *http.Request) (*http.Response, error) {
+			called = true
+			return &http.Response{
+				Status:     "Test Status",
+				StatusCode: 504,
+			}, nil
+		})
+
+		e := WithConfig(Config{
+			Client:   client,
+			Reporter: newMockReporter(t),
+		})
+
+		req := e.GET("/")
+		resp := req.Expect()
+
+		assert.True(t, called)
+		assert.Equal(t, resp.httpResp.StatusCode, 504)
+		assert.Equal(t, resp.httpResp.Status, "Test Status")
+	})
+
+	t.Run("WebsocketDialerFunc", func(t *testing.T) {
+		called := false
+		dialer := WebsocketDialerFunc(func(
+			_ string, _ http.Header,
+		) (*websocket.Conn, *http.Response, error) {
+			called = true
+			return &websocket.Conn{}, &http.Response{}, nil
+		})
+
+		e := WithConfig(Config{
+			WebsocketDialer: dialer,
+			Reporter:        newMockReporter(t),
+		})
+
+		e.GET("/path").WithWebsocketUpgrade().Expect().Websocket()
+
+		assert.True(t, called)
+	})
+
+	t.Run("ReporterFunc", func(t *testing.T) {
+		called := false
+		message := ""
+		client := ClientFunc(func(r *http.Request) (*http.Response, error) {
+			return nil, errors.New("")
+		})
+		reporter := ReporterFunc(func(_ string, _ ...interface{}) {
+			called = true
+			message = "test reporter called"
+		})
+
+		e := WithConfig(Config{
+			Reporter: reporter,
+			Client:   client,
+		})
+
+		e.GET("/").Expect()
+
+		assert.True(t, called)
+		assert.Contains(t, message, "test reporter called")
+	})
+
+	t.Run("LoggerFunc", func(t *testing.T) {
+		called := false
+		message := ""
+		logger := LoggerFunc(func(_ string, _ ...interface{}) {
+			called = true
+			message = "test logger called"
+		})
+
+		e := WithConfig(Config{
+			Reporter: newMockReporter(t),
+			Printers: []Printer{
+				NewCompactPrinter(logger),
+			},
+		})
+
+		e.GET("").Expect()
+
+		assert.True(t, called)
+		assert.Contains(t, message, "test logger called")
+	})
+}