12

I have this code:

public static bool IsValidVoucher(string id)
{
    //read tsv files
    var temp1 = AppData.GetAppData("stringval");            
    // code that need to be tested        
    return true;        
}

And I like to create unit test, but how can I mock AppData.GetAppData("stringval") to whatever I want the value is, so I can test the rest of code.

The AppData class is:

public class AppData
{
    public static object GetAppData(string name)
    {
        //...
    }
}
NASSER
  • 5,900
  • 7
  • 38
  • 57
Mr.Rendezvous
  • 1,933
  • 5
  • 20
  • 34
  • Instead of hardcoding 'AppData.GetAppData' you could inject a class which does the reading of the AppData and then in your unit test, mock that class and setup the return value you need. – LInsoDeTeh Aug 07 '15 at 07:35
  • With Moq not possible without further changes of the source code. But with [Typemock's Isolator](http://www.typemock.com/isolator-features/test-the-un-testable/fake-static-methods.html) it is possible. However Isolator is not free. – Daniel Dušek Aug 07 '15 at 07:58
  • 1
    And [here](http://adventuresdotnet.blogspot.de/2011/03/mocking-static-methods-for-unit-testing.html) some more info about mocking static methods. And [here](http://stackoverflow.com/questions/5864076/mocking-static-methods) answer from SO. HTH – Daniel Dušek Aug 07 '15 at 08:33
  • possible duplicate of [Mock static property with moq](http://stackoverflow.com/questions/2416362/mock-static-property-with-moq) – Daniel Dušek Aug 07 '15 at 08:40
  • 1
    I've dropped my answer because I believe that it won't help in this scenario. Anyway now you know that expression trees are there to help a lot! – Matías Fidemraizer Aug 10 '15 at 07:11

2 Answers2

11

Static methods cannot be mocked in an easy way. You basically have two choices:

  1. If you own the AppData class, change the implementation to implement an interface (e.g. IAppData) and remove the static modifier on the GetAppData method so you can mock it.

    public interface IAppData
    {
        object GetAppData(string id);
    }
    
    public class AppData : IAppData
    {
        public object GetAppData(string id) {}
    }
    
    public class Foo
    {
        private readonly IAppData _appData;
    
        public Foo(IAppData appData)
        {
            _appData = appData;
        }
    
        public bool IsValidVoucher(string id)
        {
            // Call through object instance instead for class reference
            var temp1 = _appData.GetAppData("stringval");
        }
    }
    
  2. If you do not own the AppData class, use a wrapper class (e.g. AppDataWrapper) that implements an interface and call that method from IsValidVoucher instead:

    public interface IAppData
    {
        object GetAppData(string id);
    }
    
    public class AppDataWrapper : IAppData
    {
        public object GetAppData(string id)
        {
            return AppData.GetAppData(id);
        }
    }
    
    public class Foo
    {
        private readonly IAppData _appData;
    
        public Foo(IAppData appData)
        {
            _appData = appData;
        }
    
        public bool IsValidVoucher(string id)
        {
            var temp1 = _appData.GetAppData("stringval");
        }
    }
    

You can then unit test Foo using Moq (using xunit as an example here):

public class FooTests
{
    private readonly IAppData _mockAppData;

    public FooTests()
    {
        var mockAppData = new Mock<IAppData>();
        mockAppData.Setup(m => m.GetAppData(It.IsAny<string>)).Returns("my test value");
        _mockAppData = mockAppData.Object;
    }

    [Fact]
    public void IsValidVoucher_ValidAppData_Returns()
    {
        var foo = new Foo(_mockAppData);
        // Unit test foo.IsValidVoucher
    }
}
rexcfnghk
  • 14,435
  • 1
  • 30
  • 57
1

Well, I think everyone's comments so far is technically correct - using something like RhinoMocks or Moq, you really can't mock static methods in a facile, straightforward manner.

But using Moles, you definitely can. So if you have significant (currently) untestable code that reside within static methods, I think you should be looking into Moles.

(This link is a bit dated but I still find it helpful) http://research.microsoft.com/en-us/projects/pex/molesmanual.pdf

(Key text)

Moles can be used to detour any .NET method, including non-virtual and static methods in sealed types.

How it works: Suppose you have a typical situation like this:

public static class SomeStaticClass
{
    public static int SomeStaticMethod(string s)
    {
        return "Static method called: " + s;
    }
}

public class SomeInstanceClass
{
    public string SomeInstanceMethod(string s)
    {
        return SomeStaticClass.SomeStaticMethod(s);
    }
}

Using Moles, your test code would look like this:

[TestMethod()]
[HostType("Moles")]
public void ShouldBeAbleToTestStaticMethod()
{
    var instance = new SomeInstanceClass();
    var testValue = instance.SomeInstanceMethod("Some test string");
    SomeStaticClass.SomeStaticMethod = (s) => "Moled you! " + s;
    Assert.That(testValue, Is.EqualTo("Moled you! Some test string"); // sorry, this has code smell, lol
}

Of course you need to set up Moles into your test project, so be sure to look it up - lots of web resources to help you on your way.

Some helpful posts:

https://msdn.microsoft.com/en-us/library/ff798308.aspx

http://adventuresdotnet.blogspot.com/2011/03/mocking-static-methods-for-unit-testing.html

https://wannabeegeek.wordpress.com/2013/03/13/unit-testing-made-easy-with-moles-part-i/

code4life
  • 15,655
  • 7
  • 50
  • 82
  • I think you should at least show some code in your answer to show how Moles can help unit testing static methods instead of simply posting links to various resources. – rexcfnghk Aug 07 '15 at 08:25
  • 1
    Is Moles still supported/available? The site seems to state that Fakes will replace it, and the assumable download link only direct me to a list where Moles isn't there. I may have missed the download link perhaps? – Adam L. S. Nov 05 '16 at 18:33
  • Here's current (VS 2017) docs on MS Fakes: https://learn.microsoft.com/en-us/visualstudio/test/isolating-code-under-test-with-microsoft-fakes Unfortunately, Fakes is only available with the Enterprise SKU of Visual Studio: https://www.visualstudio.com/vs/compare/ As for Moles, it appears that it was last supported in VS 2010: https://www.microsoft.com/en-us/research/project/moles-isolation-framework-for-net/ – Jon Schneider Sep 05 '17 at 15:17