There is a big difference in my book between starting up an in process server (requiring all sorts of grpc naming magic) running your extensions to an abstract class inside the grpc magic, and a language level interface.
On the one level you can say new X(new MyService()) or new X(mock(Service.class))) if you have to, and on the other its just loads of jibber-jabber.
There is a reason mocking is not supported: It's incredibly error prone. The previous version of gRPC (Stubby) did actually support mocking of the stub/service interfaces. The issue is that people mock out responses that don't map to reality, causing the tests to pass but the system-under-test to blow up. This happened often enough that ability to mock was ripped out, and the InProcess server-client added.
The extra "jibber-jabber" is what makes people confident that their Stub usage is correct.
Some sample bugs NOT caught by a mock:
* Calling the stub with a NULL message
* Not calling close()
* Sending invalid headers
* Ignoring deadlines
* Ignoring cancellation
There's more, but these are real bugs that are trivially caught by using a real (and cheap) server.
For Java that isn't true. Java ships with a lightweight InProcess server to stub out the responses to your client.
> Load balancing is done by maintaining multiple connections to all upstreams.
Load balancing is fully pluggable. The default balancer only picks the first connection.
> The tooling for web clients was terrible
Agreed. This is almost entirely the fault of Chrome and Firefox, for not implementing the HTTP/2 spec properly. (missing trailers).