Coder Social home page Coder Social logo

Comments (15)

holykol avatar holykol commented on May 16, 2024 26

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))
}

from fasthttp.

mithleshmeghwal-zz avatar mithleshmeghwal-zz commented on May 16, 2024 6
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)
}

from fasthttp.

valyala avatar valyala commented on May 16, 2024 3

@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.

from fasthttp.

valyala avatar valyala commented on May 16, 2024 3

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

from fasthttp.

RiskyFeryansyahP avatar RiskyFeryansyahP commented on May 16, 2024 2

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

from fasthttp.

heynemann avatar heynemann commented on May 16, 2024 2

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.

from fasthttp.

Dentrax avatar Dentrax commented on May 16, 2024 2

Dropping HTTP Server Tests article as a reference by @mstrYoda

from fasthttp.

saiumesh535 avatar saiumesh535 commented on May 16, 2024 1

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!

from fasthttp.

mathvav avatar mathvav commented on May 16, 2024

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

from fasthttp.

feliperohdee avatar feliperohdee commented on May 16, 2024

so useful! thanks!

from fasthttp.

cwoodfield avatar cwoodfield commented on May 16, 2024

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))
}

from fasthttp.

cwoodfield avatar cwoodfield commented on May 16, 2024

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

from fasthttp.

holykol avatar holykol commented on May 16, 2024

@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!")
}

from fasthttp.

Arnold1 avatar Arnold1 commented on May 16, 2024

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

from fasthttp.

Dentrax avatar Dentrax commented on May 16, 2024

@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?

from fasthttp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.