user3205479
user3205479

Reputation: 1523

Writing unit test for utility which consumes RestClient

We have decided to create an HTTP logger which can be reused across multiple projects, we created a utility which looks like this.

// pseudo-code
public class HttpLog{
      private readonly string uri;
      private readonly RestClient client;

      public HttpLog(string uri){
          this.uri = uri;
          // notice the initialization of rest client
          this.client = new RestClient();
      }

     void write(object data){
         this.client.uri = this.uri + endpoint;
         this.client.postAsync(data);
     }           
}

The consumer should provide the URI and we have exposed public write methods to log the data, however, we are unable to unit test our HttpLog class since it initializes the rest client. We are not using dependency injection since we are creating utility.

Any help would be greatly appreciated on how to refactor or unit test .write() method.

We can think of two methods

Please let us know if there is a better way to unit-test this code.

The answers below stated to use constructor overloading or making the property to public

Why I am not preferring dependency injection or constructor overload because of I strongly believe the consumer/client should not care or worry about the implementation details. They should be as much as abstraction possible. If you make them in constructor overload then you are making a way to pollute the abstraction.

For example, if you are using RestClient or HttpClient they don't ask you to provide HTTP implementation on how to write data, they simply ask you URI and data to post that is what a true abstraction is to end user.

Please correct me If my assumptions are wrong

Upvotes: 0

Views: 737

Answers (2)

Flater
Flater

Reputation: 13763

First things first: what are you testing? Your method, or the REST service itself?

Since you say "we are unable to unit test our HttpLog class", I infer you're trying to test your class.

Therefore, you should test it without the REST service (which is an external dependency). The REST client should be injected as a dependency, so it can then easily be mocked.

We are not using dependency injection since we are creating utility.

That is not a valid argument for skipping dependency injection.

Note: I infer from your statement that you know how to implement dependency injection, you've simply chosen not to. I'm going to omit an actual example of dependency injection so this answer can focus on the core problem: your decision not to use dependency injection.

  1. Constructor overload (which is not an efficient way just to unit test)

This defeats the point of testing. You're creating a different code path for your test and your (real) runtime, which means the test no longer (fully) tests the runtime code execution.

Currently, your constructor only instantiates the rest client, so you're not compromising much. But the same would not apply if the constructor did more than just that. Secondly, you'll be unable to detect any regressions if you're testing a different constructor than you actually use at runtime.

  1. making client property as public {get; set} which also breaks OOP principle.

Your second suggestion directly proves that you are willing (and trying) to inject a dependency. You're simply trying to inject it via a publically settable property instead of a constructor parameter.

You are correct that using a publically settable property is not a good decision, as it opens the door to other issues.
Comparatively, using a constructor parameter allows the same functionality (publically choosing the client) without compromising the encapsulation (not being able to change the client during the object's lifetime).

Therefore, the answer is to use dependency injection.

Upvotes: 1

nvoigt
nvoigt

Reputation: 77285

We are not using dependency injection since we are creating utility.

That is not a reason. If you use dependencies and want to be able to test it properly, you should use dependency injection. That doesn't mean you cannot provide a default implementation for normal users.

Provide a constructor overload. I have no idea why you think this would be "inefficient".

Example:

public class HttpLog{
      private readonly string uri;
      private readonly RestClient client;

      public HttpLog(string uri) : this(uri, new RestClient()){
      }

      public HttpLog(string uri, RestClient restClient){
          this.uri = uri;
          // notice the initialization of rest client
          this.client = restClient;
      }

     void write(object data){
         this.client.uri = this.uri + endpoint;
         this.client.postAsync(data);
     }           
}

Upvotes: 1

Related Questions