Supposing you have an abstract base class like this:
public abstract class WebApiServiceBase
{
public WebApiServiceBase(
HttpClient httpClient)
{
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
protected HttpClient HttpClient { get; }
// For brevity, omitted some instance methods that can be used by derived classes
}
The class knows it needs the HttpClient in the constructor so it throws an error if it's null. Should this error-throwing functionality be unit tested?
If so, what's the best way, seeing as you can't directly instantiate an abstract class in your unit tests like this:
[TestClass()]
public class WebApiServiceBaseTests
{
[TestMethod()]
public void WebApiServiceBase_NullHttpClient_ThrowsArgumentNull()
{
//Arrange
Action action = () => {
var controller = new WebApiServiceBase(null); // Won't compile, of course, because you can't directly instantiate an abstract class
};
//Act
//Assert
Assert.ThrowsException<ArgumentNullException>(action);
}
}
I understand there are many things I could do:
But what should I do? What's the best practice?
This works but it's a bit dodgy.The constructor exception doesn't happen until you try to accesss some property of the mock. It does confirm that something is throwing the exception you want though.
[TestClass]
public class UnitTest1
{
[TestMethod()]
public void WebApiServiceBase_NullHttpClient_ThrowsArgumentNull()
{
var controller = new Mock<WebApiServiceBase>(MockBehavior.Loose,
//Pass null in to the constructor
null
);
var exception = Assert.ThrowsException<TargetInvocationException>(() =>
{
var httpClient = controller.Object.HttpClient;
});
Assert.IsTrue(exception.InnerException is ArgumentNullException ane && ane.ParamName == "httpClient");
}
}
But, you shouldn't design your classes like this because it makes them hard to test. Better to inject all the reusable code instead of making a base class. A good Http client abstraction would mean that you don't need to create a base class. Here is a good abstraction:
public interface IClient
{
/// <summary>
/// Sends a strongly typed request to the server and waits for a strongly typed response
/// </summary>
/// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
/// <param name="request">The request that will be translated to a http request</param>
/// <returns>The response as the strong type specified by TResponseBody /></returns>
/// <typeparam name="TRequestBody"></typeparam>
Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(IRequest<TRequestBody> request);
/// <summary>
/// Default headers to be sent with http requests
/// </summary>
IHeadersCollection DefaultRequestHeaders { get; }
/// <summary>
/// Base Uri for the client. Any resources specified on requests will be relative to this.
/// </summary>
AbsoluteUrl BaseUri { get; }
}