connectrpc / connect-go

The Go implementation of Connect: Protobuf RPC that works.
https://connectrpc.com
Apache License 2.0
2.76k stars 90 forks source link

Add constructors for streams #731

Closed pd93 closed 1 month ago

pd93 commented 2 months ago

Fixes #719

As discussed in #719, it is currently difficult to unit test streams because there is no way to construct one with a custom/mock implementation of StreamingHandlerConn (or StreamingClientConn for client-side streams). This PR exposes new constructors for the following types:

This allows us to do something along the lines of this (example using mockgen and gomock):

//go:generate $GOBIN/mockgen -destination=stream_mock.go -package=mypackage connectrpc.com/connect StreamingHandlerConn

ctrl := gomock.NewController(t)
mockStreamingHandlerConn := NewMockStreamingHandlerConn(ctrl)
mockStreamingHandlerConn.EXPECT().Send(
    // My expected stream message...
).Return(nil)

// New constructor allows us to create a ServerStream using our mock
stream := connect.NewServerStream[controllerv3.Requirements](mockStreamingHandlerConn)

// call service with mock stream
err := myservice.MyRPC(context.Background(), req, stream)

As requested here, I have also updated the internal code to use these new constructors. One thing I wasn't sure about is whether or not we'd want to expose any other parameters. There are a few cases internally where a stream is constructed with additional fields (such as a maybeInitializer) and not being able to pass one into the constructor leads to code like this:

stream := NewClientStreamForClient[Req, Res](c.newConn(ctx, StreamTypeClient, nil))
stream.initializer = c.config.Initializer
return stream

However, my use case doesn't have a need to pass in any other parameters and it keeps the API nice and clean without. I think the style above is fine for internal code. Happy to discuss this if there is a need for it though.

emcfarlane commented 1 month ago

@pd93 thanks for looking into this issue. Closing for now as we need to discuss the API implementation further before looking at an implementation.