Search code examples
c#unit-testingmstest

Should you unit test constructors of abstract classes and, if so, how?


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:

  • Only test the error-throwing functionality in derived classes.
  • Invent a new derived class in the test code just for testing this.
  • Don't make classes like this abstract in the first place.
  • Probably some other ideas I haven't thought of too.

But what should I do? What's the best practice?


Solution

  • 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; }
    }
    

    Reference