How to Mock a Static Singleton?

后端 未结 7 685
伪装坚强ぢ
伪装坚强ぢ 2020-12-15 23:05

I have number of classes I\'ve been asked to add some unit tests to with Rhino Mocks and having some issues.

First off, I know RhinoMocks doesn\'t allow for the mock

相关标签:
7条回答
  • 2020-12-15 23:19

    Here's a low-touch approach that uses a delegate, which can be set initially and changed at runtime. It's better explained by example (specifically, mocking DateTime.Now):

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

    0 讨论(0)
  • 2020-12-15 23:19

    Example from Book: Working Effectively with Legacy Code

    To run code containing singletons in a test harness, we have to relax the singleton property. Here’s how we do it. The first step is to add a new static method to the singleton class. The method allows us to replace the static instance in the singleton. We’ll call it setTestingInstance.

    public class PermitRepository
    {
        private static PermitRepository instance = null;
        private PermitRepository() {}
        public static void setTestingInstance(PermitRepository newInstance)
        {
            instance = newInstance;
        }
        public static PermitRepository getInstance()
        {
            if (instance == null) 
            {
                instance = new PermitRepository();
            }
            return instance;
        }
        public Permit findAssociatedPermit(PermitNotice notice) 
        {
        ...
        }
    ...
    }
    

    Now that we have that setter, we can create a testing instance of a PermitRepository and set it. We’d like to write code like this in our test setup:

    public void setUp() {
    PermitRepository repository = new PermitRepository();
    ...
    // add permits to the repository here
    ...
    PermitRepository.setTestingInstance(repository);
    }
    
    0 讨论(0)
  • 2020-12-15 23:24

    Check out Dependency Injection.

    You've already began this, but for hard to test classes (statics etc...) you can use the adapter design pattern to write a wrapper around this hard to test code. Using the interface of this adapter, you can then test your code in isolation.

    For any unit testing advice, and further testing issues check out the Google Testing Blog, specifically Misko's articles.

    Instance

    You say you are writing tests, so it may be too late, but could you refactor the static to the instance? Or is there a genuine reason why said class should remain a static?

    0 讨论(0)
  • 2020-12-15 23:25

    Discouraged by threads like this, it took me quite some time to notice, that singletons are not that hard to mock. After all why are we using c#?

    Just use Reflection.

    With provided sample code you need to make sure the static constructor is called before setting the static field to the mocked object. Otherwise it might overwrite your mocked object. Just call anything on the singleton that has no effect before setting up the test.

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

    I provided code for mocking with Moq, but I guess Rhino Mocks is quite similar.

    0 讨论(0)
  • 2020-12-15 23:28

    You can mock the interface, ISomeInterface. Then, refactor the code that uses it to use dependency injection to get the reference to the singleton object. I have come across this problem many times in our code and I like this solution the best.

    for example:

    public class UseTheSingleton
    {
        private ISomeInterface myX;
    
        public UseTheSingleton(ISomeInterface x)
        {
            myX = x;
        }
    
        public void SomeMethod()
        {
            myX.
        }
    }
    

    Then ...

    UseTheSingleton useIt = UseTheSingleton(Example1.Instance);
    
    0 讨论(0)
  • 2020-12-15 23:37

    Singletons are at odds with Testability because they are so hard to change. You would be much better off using Dependency Injection to inject an ISomeInterface instance into your consuming classes:

    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
    }
    

    Notice how the Guard Claus together with the readonly keyword guarantees that the ISomeInterface instance will always be available.

    This will allow you to use Rhino Mocks or another dynamic mock library to inject Test Doubles of ISomeInterface into the consuming classes.

    0 讨论(0)
提交回复
热议问题