4

I am trying to write my first "real" library.
In my case it will provide a java/kotlin API for sending network calls to a server.

I am a big fan of TDD and would like to code the library in the same way.
Normally I would now separate the networking code from the other code to test if the networking class gets called correctly

public class SpecialServerClient {

    private final HttpClient httpClient;

    public SpecialServerClient(HttpClient httpClient){
        this.httpClient = httpClient;
    }

    public void sendData(Data data){
        //Do things with data
        //Create request for data
        //Pass request to httpClient
    }
}

and the test

public class SpecialServerClientTest {

    @Mock HttpClient httpClient;

    private SpecialServerClient specialServerClient;

    @Before
    public void setUp(){
        specialServerClient = new SpecialServerClient(httpClient)
    }

    @Test
    public void testSendData(){
        Data data = //Create fake data

        specialServerClient.sendData(data);

        verify(httpClient).calledWithRequestWithTransformedData()
    }
}

But in the case of a library I cannot quite do that.
I do not want to burden the users of the API to provide a HttpClient even if the library provides a default/real implementation for them.

Of course I could create an overloaded constructor just for testing:

public class SpecialServerClient {

    private final HttpClient httpClient;

    public SpecialServerClient(){
        this.httpClient = RealHttpClient();
    }

    public SpecialServerClient(HttpClient httpClient){
        this.httpClient = httpClient;
    }
}

but now I am adding production code for the sake of testing,
which is not that great either.

Is there a better way to solve this?

  • Such a great question! I haven't found anyone provide the one and only answer. See this article for how I'm doing it. – John Wu Jul 31 '17 at 21:39
8

I do not want to burden the users of the API to provide a HttpClient even if the library provides a default/real implementation for them.

How is it a burden on the user if you're providing a default dependency (as you do with the zero-arg constructor)?

but now I am adding production code for the sake of testing,

It's not only for the sake of testing. By exposing the DI method, you're making your library more flexible. It's highly likely that some clients will want to configure their http client themselves. For example it's a very common requirement to configure things like SSL and timeouts on the client. They may not want to use RealHttpClient, and the user may want to mock this in their own integration tests.

What are the downsides to having the DI constructor?

  • Thanks, this sounds right to me. I think it is just weird to write code that is "not used" anywhere, but thats the case with all entry points to libraries I guess. – Altoyr Aug 1 '17 at 8:06
1

Looking at SpecialServerClient only, it is correct to depend on a HttpClient. It should not be concerned with how a HttpClient is created. If you don't want to "burden" the library users of choosing/creating a HttpClient, you can provide a convenience factory that creates SpecialServerClient with a default HttpClient. You essentially move the object creation to a different place (if you are okay with not testing the factory itself).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.