开发者

Simulate a tcp connection in Go

开发者 https://www.devze.com 2022-12-15 05:14 出处:网络
In Go, a TCP connection (net.Conn) is a io.ReadWriteCloser. I\'d like to test my network co开发者_Python百科de by simulating a TCP connection. There are two requirements that I have:

In Go, a TCP connection (net.Conn) is a io.ReadWriteCloser. I'd like to test my network co开发者_Python百科de by simulating a TCP connection. There are two requirements that I have:

  1. the data to be read is stored in a string
  2. whenever data is written, I'd like it to be stored in some kind of buffer which I can access later

Is there a data structure for this, or an easy way to make one?


No idea if this existed when the question was asked, but you probably want net.Pipe() which provides you with two full duplex net.Conn instances which are linked to each other


EDIT: I've rolled this answer into a package which makes things a bit simpler - see here: https://github.com/jordwest/mock-conn


While Ivan's solution will work for simple cases, keep in mind that a real TCP connection is actually two buffers, or rather pipes. For example:

 Server   |   Client
 ---------+---------
  reads <===  writes
 writes ===>  reads

If you use a single buffer that the server both reads from and writes to, you could end up with the server talking to itself.

Here's a solution that allows you to pass a MockConn type as a ReadWriteCloser to the server. The Read, Write and Close functions simply proxy through to the functions on the server's end of the pipes.

type MockConn struct {
    ServerReader *io.PipeReader
    ServerWriter *io.PipeWriter

    ClientReader *io.PipeReader
    ClientWriter *io.PipeWriter
}

func (c MockConn) Close() error {
    if err := c.ServerWriter.Close(); err != nil {
        return err
    }
    if err := c.ServerReader.Close(); err != nil {
        return err
    }
    return nil
}

func (c MockConn) Read(data []byte) (n int, err error)  { return c.ServerReader.Read(data) }
func (c MockConn) Write(data []byte) (n int, err error) { return c.ServerWriter.Write(data) }

func NewMockConn() MockConn {
    serverRead, clientWrite := io.Pipe()
    clientRead, serverWrite := io.Pipe()

    return MockConn{
        ServerReader: serverRead,
        ServerWriter: serverWrite,
        ClientReader: clientRead,
        ClientWriter: clientWrite,
    }
}

When mocking a 'server' connection, simply pass the MockConn in place of where you would use the net.Conn (this obviously implements the ReadWriteCloser interface only, you could easily add dummy methods for LocalAddr() etc if you need to support the full net.Conn interface)

In your tests you can act as the client by reading and writing to the ClientReader and ClientWriter fields as needed:

func TestTalkToServer(t *testing.T) {
    /*
     * Assumes that NewMockConn has already been called and
     * the server is waiting for incoming data
     */

    // Send a message to the server
    fmt.Fprintf(mockConn.ClientWriter, "Hello from client!\n")

    // Wait for the response from the server
    rd := bufio.NewReader(mockConn.ClientReader)
    line, err := rd.ReadString('\n')

    if line != "Hello from server!" {
        t.Errorf("Server response not as expected: %s\n", line)
    }
}


Why not using bytes.Buffer? It's an io.ReadWriter and has a String method to get the stored data. If you need to make it an io.ReadWriteCloser, you could define you own type:

type CloseableBuffer struct {
    bytes.Buffer
}

and define a Close method:

func (b *CloseableBuffer) Close() error {
    return nil
}


In majority of the cases you do not need to mock net.Conn.

You only have to mock stuff that will add time to your tests, prevent tests from running in parallel (using shared resources like the hardcoded file name) or can lead to outages (you can potentially exhaust the connection limit or ports but in most of the cases it is not a concern, when you run your tests in isolation).

Not mocking has an advantage of more precise testing of what you want to test with a real thing.

https://www.accenture.com/us-en/blogs/software-engineering-blog/to-mock-or-not-to-mock-is-that-even-a-question

Instead of mocking net.Conn, you can write a mock server, run it in a goroutine in your test and connect to it using real net.Conn

A quick and dirty example:

port := someRandomPort()
srv := &http.Server{Addr: port}
go func(msg string) {
    http.HandleFunc("/hello", myHandleFUnc)
    srv.ListenAndServe()
}
myTestCodeUsingConn(port)
srv.Shutdown(context.TODO())
0

精彩评论

暂无评论...
验证码 换一张
取 消