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

How do you handle unit tests? #36

Closed
mathvav opened this issue Jan 9, 2016 · 15 comments
Closed

How do you handle unit tests? #36

mathvav opened this issue Jan 9, 2016 · 15 comments
Labels

Comments

@mathvav
Copy link

mathvav commented Jan 9, 2016

I've been exploring full-stack unit testing for apps built on fasthttp, and my initial instinct was to use the default http package (to reduce the chance of a fasthttp flaw being shared between client and server, causing something to go undetected). However, Go doesn't like sharing a TCP port:

--- FAIL: TestServer (0.00s)
panic: listen tcp :8080: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. [recovered]
        panic: listen tcp :8080: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.

You seem to use unexported functions to do internal testing. What's the recommended way to do testing without running into errors with these? I'm doing a server.ListenAndServe(":8080") for the server and then doing this to connect to the localhost server:

req, err := http.NewRequest(GET, "localhost:8080/hostTest", nil)
if err != nil {
    panic(err)
}
req.Host = "example.com"
_, err = c.Do(req)
panicErr(err)
// Validate the response here...

I've been considering mocking up the net.Listener interface and passing that to Server.Serve(). Is there a better solution? What do you suggest?

@mathvav
Copy link
Author

mathvav commented Jan 9, 2016

If there's not an elegant solution, I've started messing around with mocking up an object that would be a fairly decent replacement.

@valyala
Copy link
Owner

valyala commented Jan 11, 2016

@Annonomus-Penguin , there are many approaches:

1. Use distinct TCP port for each test:

func TestFoo(t *testing.T) {
    port := 1234
    defer startServerOnPort(t, port, requestHandler).Close()

    // your tests here for client connecting to the given port
}

func TestBar(t *testing.T) {
    port := 1235  // note - the port differs from TestFoo.
    defer startServerOnPort(t, port, requestHandler).Close()

    // your tests here for client connecting to the given port
}

func startServerOnPort(t *testing.T, port int, h fasthttp.RequestHandler) io.Closer {
    ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
    if err != nil {
        t.Fatalf("cannot start tcp server on port %d: %s", port, err)
    }
    go fasthttp.Serve(ln, h)
    return ln
}

2. Use unix sockets instead of TCP sockets. It is advisable to use distinct unix socket addresses (file paths) for each test:

func TestBar(t *testing.T) {
    filepath := "/tmp/TestBar.sock"
    defer startUnixServer(t, filepath, requestHandler).Close()

    // now you must override Client.Transport.Dial for connecting to filepath.
    // see http://stackoverflow.com/a/26224019/274937 for details.
}

func startUnixServer(t *testing.T, filepath string, h fasthttp.RequestHandler) io.Closer {
    ln, err := net.Listen("unix", filepath)
    if err != nil {
        t.Fatalf("cannot start unix server on %q: %s", filepath, err)
    }
    go fasthttp.Serve(ln, h)
    return ln
}

3. Create custom net.Listener, which also implements Transport.Dial and use net.Listener part in server and Transport.Dial part in client. I didn't test the following code, but you must get the idea:

type listenerDialer struct {
    // server conns to accept
    conns chan net.Conn
}

func NewListenerDialer() *listenerDialer {
    ld := &listenerDialer{
        conns: make(chan net.Conn),
    }
    return ld
}

// net.Listener interface implementation
func (ld *listenerDialer) Accept() (net.Addr, error) {
    conn, ok := <-ld.conns
    if !ok {
        return nil, errors.New("listenerDialer is closed")
    }
    return conn, nil
}

// net.Listener interface implementation
func (ld *listenerDialer) Close() error {
    close(ld.conns)
    return nil
}

// net.Listener interface implementation
func (ld *listenerDialer) Addr() net.Addr {
    // return arbitrary fake addr.
    return &net.UnixAddr{
        Name: "listenerDialer",
        Net: "fake",
    }
}

// Transport.Dial implementation
func (ld *listenerDialer) Dial(network, addr string) (net.Conn, error) {
    cConn, sConn := net.Pipe()
    ld.conns <- sConn
    return cConn, nil
}

IMHO, the last approach is the best, since it doesn't limit you with "distinct TCP ports and/or unix socket file paths per test" rule.

valyala added a commit that referenced this issue Jan 11, 2016
…nt<->server tests and fast in-process client<->server communication
@valyala
Copy link
Owner

valyala commented Jan 11, 2016

@Annonomus-Penguin , just added InmemoryListener, which implements the last approach from my comment above.

@valyala valyala closed this as completed Jan 11, 2016
@feliperohdee
Copy link

so useful! thanks!

zwirec pushed a commit to zwirec/fasthttp that referenced this issue Jul 12, 2018
@cwoodfield
Copy link

cwoodfield commented Dec 3, 2018

Came across this closed issue, but I wanted to contribute my solution. I was able to steal^H^H^H^H^Hleverage some code found in server_test.go to build a handler that my individual unit tests can call. Feel free to adapt to your needs.

// First, implement a ReadWriter and necessary methods
type readWriter struct {
	net.Conn
	r bytes.Buffer
	w bytes.Buffer
}

func (rw *readWriter) Close() error {
	return nil
}

func (rw *readWriter) Read(b []byte) (int, error) {
	return rw.r.Read(b)
}

func (rw *readWriter) Write(b []byte) (int, error) {
	return rw.w.Write(b)
}

func (rw *readWriter) RemoteAddr() net.Addr {
	return zeroTCPAddr
}

func (rw *readWriter) LocalAddr() net.Addr {
	return zeroTCPAddr
}

func (rw *readWriter) SetReadDeadline(t time.Time) error {
	return nil
}

func (rw *readWriter) SetWriteDeadline(t time.Time) error {
	return nil
}

// Currently I'm only looking at Status code and the response body. Feel free to add a header item.
type testHttpResponse struct {
	code int
	body []byte
}

// HTTPTestHandler - URL path as arg, returns testHttpResponse struct (code and response body).
func HTTPTestHandler(t *testing.T, path string, timeout int) (*testHttpResponse, error) {
	s := &fasthttp.Server{
		Handler: router,
	}

        // default timeout is 10s
        if timeout == 0 {
                timeout = 10
        }

	requestString := fmt.Sprintf("GET %v HTTP/1.1\r\nHost: localhost\r\n\r\n", path)
	rw := &readWriter{}
	rw.r.WriteString(requestString)

        // Later on in this function we'll be calling http.ReadResponse to parse the raw HTTP output.
	// http.ReadResponse requires a http.Request object as an arg, so we'll create one here.
	req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader([]byte(requestString))))

	ch := make(chan error)
	go func() {
		ch <- s.ServeConn(rw)
	}()

	select {
	case err := <-ch:
		if err != nil {
			t.Fatalf("Unexpected error from serveConn: %s", err)
		}
	case <-time.After(timeout * time.Second):
		t.Errorf("timeout")
	}

	resp, err := ioutil.ReadAll(&rw.w)
	if err != nil {
		t.Fatalf("Unexpected error from ReadAll: %s", err)
	}

	// And parse the returning text to a http.Response object
	httpResp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(resp)), req)

	code := httpResp.StatusCode
	body, err := ioutil.ReadAll(httpResp.Body)
	if err != nil {
		t.Fatalf("Error reading HTTP response body")
	}

	return &testHttpResponse{code, body}, nil
}

// Test_healthCheckURL - Sample test leveraging HTTPTestHandler()
func Test_healthCheckURL(t *testing.T) {
	url := "/healthcheck"

	// Verify the status code is what we expect.
	resp, err := HTTPTestHandler(t, url)
	if err != nil {
		t.Fatal(err)
	}
	// Check response code and body
	assert.Equal(t, http.StatusOK, resp.code)
	assert.Equal(t, "OK", string(resp.body))
}

@holykol
Copy link

holykol commented Dec 15, 2018

Here is more simple solution using net/http for test requests.
I hope this will save someone time.

// serve serves http request using provided fasthttp handler
func serve(handler fasthttp.RequestHandler, req *http.Request) (*http.Response, error) {
	ln := fasthttputil.NewInmemoryListener()
	defer ln.Close()

	go func() {
		err := fasthttp.Serve(ln, handler)
		if err != nil {
			panic(fmt.Errorf("failed to serve: %v", err))
		}
	}()

	client := http.Client{
		Transport: &http.Transport{
			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
				return ln.Dial()
			},
		},
	}

	return client.Do(req)
}


// Example usage
func TestHandler(t *testing.T) {
	r, err := http.NewRequest("POST", "http://test/", nil)
	if err != nil {
		t.Error(err)
	}

	res, err := serve(MyHandler, r)
	if err != nil {
		t.Error(err)
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		t.Error(err)
	}

	fmt.Println(string(body))
}

@cwoodfield
Copy link

@holykol Your code is missing the declaration for MyHandler, can you update?

@holykol
Copy link

holykol commented Dec 17, 2018

@cwoodfield
Sorry, MyHandler is unclear. This is actually your handler that you want to test. For example:

func MyHandler(ctx *fasthttp.RequestCtx) { // Actually yours
	fmt.Fprint(ctx, "It's working!")
}

@Arnold1
Copy link

Arnold1 commented Apr 15, 2019

@holykol is it ok to use http.NewRequest with fasthttp and func serve(handler fasthttp.RequestHandler, req *http.Request) (*http.Response, error) { for testing?

@mithleshmeghwal-zz
Copy link

mithleshmeghwal-zz commented Aug 28, 2019

func serve(handler fasthttp.RequestHandler, req *fasthttp.Request, res *fasthttp.Response) error {
	ln := fasthttputil.NewInmemoryListener()
	defer ln.Close()

	go func() {
		err := fasthttp.Serve(ln, handler)
		if err != nil {
			logger.Error(err)
			panic(err)
		}
	}()

	client := fasthttp.Client{
			Dial: func(addr string) (net.Conn, error) {
				return ln.Dial()
			},
	}

	return client.Do(req, res)
}

func TestListAllSubscription(t *testing.T) {
	req := fasthttp.AcquireRequest()
	req.SetRequestURI("/uri") // task URI
	req.Header.SetMethod("GET")
	req.Header.Set("Authorization", "Basic eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTMxNDg2ODYwMjAwMDAxNyIsImFjY2Vzc190b2tlbiI6IkVBQUduN3QySWVCd0JBT1BvRVFPbGRBb0VLYTZVaWVKRlpBeDJzdnJ3V0lkTm40OFhscGR5MnhPVjdQbmtqTHc5bXhqNmpxSHZkaE9qdjhLNGtMdkRnRjdyRHZaQ2I0TmMwNXZHQlFUQkgxa3ozN3FMRXVVemdaQk9QMDNYWkFnR3dXcDE5bFhROEVpWkJYaW1aQVpCTzNYVVNwdno5Nm9GQmVmT3VUQnFnZmlMQVpEWkQifQ.L9WfIAS67TYESg4k-wanTV0gOJGEPEPG2ixIgPrCb68")
	req.Header.SetContentType("application/json")
	
	resp := fasthttp.AcquireResponse()
	err := serve(handler, req, resp)
	if err != nil {

	}
	logger.Info("resp from Postman.Post: ", resp)
	logger.Info("resp status code", resp.StatusCode())
	logger.Info("resp body", string(resp.Body()))
}
func handler(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusOK)
}

@RiskyFeryansyahP
Copy link

RiskyFeryansyahP commented Apr 3, 2020

hello, i want ask, How to do unit testing if the url have url parameters like /:id ?

@Dentrax
Copy link

Dentrax commented Apr 10, 2020

@holykol @mithleshmeghwal

Thanks for the examples. :) I tried to implement custom Handler() method that return should custom status and body. But somehow, I couldn't make it work as i expected.

func MyHandler(handler fasthttp.RequestHandler) fasthttp.RequestHandler {
	return func(ctx *fasthttp.RequestCtx) {
		handler(ctx)

		fmt.Fprintf(ctx, "URI: %q", ctx.RequestURI())

		ctx.Response.ResetBody()
		ctx.Response.SetStatusCode(fasthttp.StatusBadGateway)
		ctx.Response.SetBodyString("MY TEST BODY")
	}
}

I'm calling this body in this way:

h := fasthttp.FSHandler("/test", 0)
h = MyHandler(h)

_ = serve(h, req, res)

But still, even when these set, i'm getting 200 and null body:

res status: 200
res body: []

@valyala I'm trying to create a custom mock api server which returns custom static json bodies per endpoint. For example, I want to send 100 different mock body data for 100 endpoints with using NewInmemoryListener() function, however, i do not want to call serve function as much as the endpoints I use, singleton server pattern should be fine i guess.

If i can not do it this way; as described here, i can create a mock function for Do:

type MockClient struct {
	DoFunc func(req *http.Request) (*http.Response, error)
}

What do you recommend?

@saiumesh535
Copy link

Here is more simple solution using net/http for test requests.
I hope this will save someone time.

// serve serves http request using provided fasthttp handler
func serve(handler fasthttp.RequestHandler, req *http.Request) (*http.Response, error) {
	ln := fasthttputil.NewInmemoryListener()
	defer ln.Close()

	go func() {
		err := fasthttp.Serve(ln, handler)
		if err != nil {
			panic(fmt.Errorf("failed to serve: %v", err))
		}
	}()

	client := http.Client{
		Transport: &http.Transport{
			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
				return ln.Dial()
			},
		},
	}

	return client.Do(req)
}


// Example usage
func TestHandler(t *testing.T) {
	r, err := http.NewRequest("POST", "http://test/", nil)
	if err != nil {
		t.Error(err)
	}

	res, err := serve(MyHandler, r)
	if err != nil {
		t.Error(err)
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		t.Error(err)
	}

	fmt.Println(string(body))
}

This worked like charm. Thanks!

@heynemann
Copy link

heynemann commented Dec 22, 2021

Coming late to the party, but using @mithleshmeghwal I created helper functions like the one used in the below test:

func TestHealthcheck(t *testing.T) {
	path := "/healthcheck"
	app := NewApp()

	resp, err := testutils.TestGet(app, path)

	assert.NoError(t, err)
	assert.NotNil(t, resp)
	assert.Equal(t, 200, resp.StatusCode())
	assert.Equal(t, "WORKING", string(resp.Body()))
}

The testutils package includes such methods (TestGet) and is described below:

package testutils

import (
	"net"

	routing "github.com/qiangxue/fasthttp-routing"
	"github.com/valyala/fasthttp"
	"github.com/valyala/fasthttp/fasthttputil"
)

func serve(app TestApplication, req *fasthttp.Request, res *fasthttp.Response) error {
	ln := fasthttputil.NewInmemoryListener()
	router := app.GetRouter()
	defer ln.Close()

	go func() {
		err := fasthttp.Serve(ln, router.HandleRequest)
		if err != nil {
			panic(err)
		}
	}()

	client := fasthttp.Client{
		Dial: func(addr string) (net.Conn, error) {
			return ln.Dial()
		},
	}

	return client.Do(req, res)
}

//TestApplication interface
type TestApplication interface {
	GetRouter() *routing.Router
}

//TestGet function - Simple to add headers and stuff later on, or you could receive a req object
func TestGet(app TestApplication, path string) (*fasthttp.Response, error) {
	req := fasthttp.AcquireRequest()
	req.SetRequestURI(path) // task URI
	req.Header.SetMethod("GET")
	req.Header.Set("Host", "localhost")
	req.Header.SetContentType("application/json")

	resp := fasthttp.AcquireResponse()
	err := serve(app, req, resp)

	return resp, err
}

Should be fairly simple to remove the TestApplication interface if you want but I like the abstraction.

@Dentrax
Copy link

Dentrax commented Sep 12, 2022

Dropping HTTP Server Tests article as a reference by @mstrYoda

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

No branches or pull requests