如何模拟静态单例?

Jam*_*ers 14 c# unit-testing rhino-mocks

我有一些课程,我被要求添加一些单元测试与Rhino Mocks并有一些问题.

首先,我知道RhinoMocks不允许模拟静态成员.我正在寻找我的选择(除了使用TypeMock).

我所拥有的类的示例与以下类似:

class Example1 : ISomeInterface
{
    private static ISomeInterface _instance;

    private Example1()
    {
        // set properties via private static methods
    }

    static Example1()
    {
        _instance = new Example1();
    }

    public static ISomeInterface Instance() 
    {
        get { return _instance; }
    }

    // Instance properties 

    // Other Instance Properties that represent objects that follow a similar pattern.
}
Run Code Online (Sandbox Code Playgroud)

所以当我打电话给上面的课时,它看起来像这样......

Example1.Instance.SomeObject.GoDownARabbitHole();
Run Code Online (Sandbox Code Playgroud)

有没有办法让我SomeObject.GoDownARabbitHole()在这种情况下嘲笑或嘲笑实例?

No *_*wer 24

由于这样的线索气馁,我花了很长时间才注意到,单身人士并不难以嘲笑.毕竟为什么我们使用c#?

只需使用Reflection.

使用提供的示例代码,您需要确保在将静态字段设置为模拟对象之前调用静态构造函数.否则它可能会覆盖您的模拟对象.在设置测试之前,只需在单例上调用任何无效的东西.

ISomeInterface unused = Singleton.Instance();

System.Reflection.FieldInfo instance = typeof(Example1).GetField("_instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);

Mock<ISomeInterface> mockSingleton = new Mock<ISomeInterface>();
instance.SetValue(null, mockSingleton.Object);
Run Code Online (Sandbox Code Playgroud)

我提供了使用Moq进行模拟的代码,但我猜Rhino Mocks非常相似.

  • 问题是大多数单身人士不是基于接口.我会说很少见.如果有一个接口,那么最好不要使用单例,而是使用依赖注入. (3认同)
  • 要添加一项。一旦它被模拟,您的其他测试也将使用该模拟。你可能不想要那样。在测试结束时,您应该使用以下方法删除模拟:instance.SetValue("_instance", null); (2认同)

Mar*_*ann 12

单身人士与可测试性不一致,因为他们很难改变.使用依赖注入将ISomeInterface实例注入到消费类中会更好:

public class MyClass
{
    private readonly ISomeInterface dependency;

    public MyClass(ISomeInterface dependency)
    {
        if(dependency == null)
        {
            throw new ArgumentNullException("dependency");
        }

        this.dependency = dependency;
    }

    // use this.dependency in other members
}
Run Code Online (Sandbox Code Playgroud)

请注意Guard Claus如何与readonly关键字一起保证ISomeInterface实例始终可用.

这将允许您使用Rhino Mocks或其他动态模拟库将ISomeInterface的Test Doubles注入到消费类中.


Pet*_*ale 6

这是一种使用委托的低触摸方法,可以在最初设置并在运行时更改.通过示例更好地解释(具体来说,模拟DateTime.Now):

http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/11/09/systemtime-versus-isystemclock-dependencies-revisited.aspx


Teo*_*ahi 5

书中示例有效处理遗留代码

\n\n

要在测试工具中运行包含单例的代码,我们必须放宽单例属性。这里\xe2\x80\x99是我们如何做的。第一步是向单例类添加一个新的静态方法。该方法允许我们替换单例中的静态实例。我们\xe2\x80\x99将其称为\n setTestingInstance

\n\n
public class PermitRepository\n{\n    private static PermitRepository instance = null;\n    private PermitRepository() {}\n    public static void setTestingInstance(PermitRepository newInstance)\n    {\n        instance = newInstance;\n    }\n    public static PermitRepository getInstance()\n    {\n        if (instance == null) \n        {\n            instance = new PermitRepository();\n        }\n        return instance;\n    }\n    public Permit findAssociatedPermit(PermitNotice notice) \n    {\n    ...\n    }\n...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们有了该设置器,我们可以创建 PermitRepository 的测试实例并对其进行设置。我们\xe2\x80\x99d喜欢在我们的测试设置中编写这样的代码:

\n\n
public void setUp() {\nPermitRepository repository = new PermitRepository();\n...\n// add permits to the repository here\n...\nPermitRepository.setTestingInstance(repository);\n}\n
Run Code Online (Sandbox Code Playgroud)\n