这个问题可以解决,但是你使用了错误的模式。您需要创建一个完全替换具体依赖项的接口,而不是通过新接口公开应用程序的实例。
你有什么
如果我正确理解您的问题,您有一个密封的 Application 类,其中包含您的程序需要能够调用的一些方法,并且它没有公共构造函数,只有一个静态工厂方法。这是一个简单的讨论示例,只有一种方法,SomeMethod()。
public sealed class Application
{
//private ctor prevents anyone from using new to create this
private Application()
{
}
//Here's the method we want to mock
public void SomeMethod(string input)
{
//Implementation that needs to be stubbed or mocked away for testing purposes
}
//Static factory method
static public Application GetInstance()
{
return new Application();
}
}
你尝试了什么
您所做的可能如下所示:
interface IApplication
{
Application Application { get; }
}
class ApplicationWrapper : IApplication
{
protected readonly Application _application;
public ApplicationWrapper()
{
_application = Application.GetInstance();
}
public Application Application
{
get { return _application; }
}
}
所以在你的主代码中,你可以这样做:
var a = new ApplicationWrapper();
a.Application.SomeMethod("Real argument");
这种方法永远不会用于单元测试,因为您仍然直接依赖于密封的 Application 类。你刚刚移动了它。还是需要调用Application.SomeMethod(),具体方法;你应该只依赖于界面,而不是任何具体的东西。
什么会起作用
理论上,执行此操作的“正确”方法是包装所有内容。因此,与其将Application 公开为属性,不如将其保密;相反,您公开方法的包装版本,如下所示:
public interface IApplication
{
void SomeMethod(string input);
}
public class ApplicationWrapper : IApplication
{
protected readonly Application _application;
public ApplicationWrapper()
{
_application = Application.GetInstance();
}
public void SomeMethod(string input)
{
_application.SomeMethod(input);
}
}
那么你可以这样称呼它:
var a = new ApplicationWrapper();
a.SomeMethod("Real argument");
或者在带有 DI 的完整课程中,它看起来像这样:
class ClassUnderTest
{
protected readonly IApplication _application; //Injected
public ClassUnderTest(IApplication application)
{
_application = application; //constructor injection
}
public void MethodUnderTest()
{
_application.SomeMethod("Real argument");
}
}
如何进行单元测试
在您的单元测试中,您现在可以使用新类模拟 IApplication,例如
class ApplicationStub : IApplication
{
public string TestResult { get; set; } //Doesn't exist in system under test
public void SomeMethod(string input)
{
this.TestResult = input;
}
}
请注意,此类完全不依赖于 Application。所以你不再需要在它上面调用new,或者调用它的工厂方法。出于单元测试的目的,您只需要确保它被正确调用。您可以通过传入存根并随后检查TestResult 来做到这一点:
//Arrange
var stub = new ApplicationStub();
var c = ClassUnderTest(stub);
//Act
c.MethodUnderTest("Test Argument");
//Assert
Assert.AreEqual(stub.TestResult, "Test Argument");
编写完整的包装器需要更多的工作(特别是如果它有很多方法),但您可以使用反射或第三方工具生成大量代码。它还允许您进行完整的单元测试,这就是切换到 IApplication 界面背后的全部想法。
TLDR:
代替
IApplication wrapper = new ApplicationWrapper();
wrapper.Application.SomeMethod();
你应该使用
IApplication wrapper = new ApplicationWrapper();
wrapper.SomeMethod();
去除对具体类型的依赖。