应该对抽象类的构造函数进行单元测试吗?如果是,如何进行?



假设你有一个这样的抽象基类:

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
}

类知道它在构造函数中需要HttpClient,所以如果它为空就抛出错误。这个错误抛出功能应该进行单元测试吗?

如果是,最好的方法是什么,因为你不能像这样在单元测试中直接实例化一个抽象类:

[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);
}
}

我明白有很多事情我可以做:

  • 只测试派生类中的错误抛出功能。
  • 在测试代码中创建一个新的派生类来测试它。
  • 一开始就不要让这样的类抽象。
  • 可能还有一些我没有想到的想法。

但是我该怎么做呢?最佳实践是什么?

这是可行的,但它有点危险。直到尝试访问模拟的某些属性时,才会发生构造函数异常。但是,它确实确认某些东西正在抛出您想要的异常。

[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");
}
}

但是,你不应该这样设计你的类,因为这会使它们难以测试。最好注入所有可重用的代码,而不是创建一个基类。一个好的Http客户端抽象意味着您不需要创建基类。下面是一个很好的抽象:

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

参考

我可能会选择第二个选项(仅为测试目的创建派生类)。如果您使用了来自您的领域的派生类,那么以后当抽象类仍被其他人使用时,派生类可能会被删除。然后测试将不再编译。因此,为这个显式测试用例创建一个类会更有意义。

相关内容

最新更新