如何使用 MOQ 框架模拟 c # 中的静态方法?

我最近一直在做单元测试,我已经成功地模拟了使用 MOQ 框架和 MS 测试的各种场景。我知道我们不能测试私有方法,但我想知道我们是否可以使用 MOQ 模拟静态方法。

147516 次浏览

Moq (and other DynamicProxy-based mocking frameworks) are unable to mock anything that is not a virtual or abstract method.

Sealed/static classes/methods can only be faked with Profiler API based tools, like Typemock (commercial) or Microsoft Moles (free, known as Fakes in Visual Studio 2012 Ultimate /2013 /2015).

Alternatively, you could refactor your design to abstract calls to static methods, and provide this abstraction to your class via dependency injection. Then you'd not only have a better design, it will be testable with free tools, like Moq.

A common pattern to allow testability can be applied without using any tools altogether. Consider the following method:

public class MyClass
{
public string[] GetMyData(string fileName)
{
string[] data = FileUtil.ReadDataFromFile(fileName);
return data;
}
}

Instead of trying to mock FileUtil.ReadDataFromFile, you could wrap it in a protected virtual method, like this:

public class MyClass
{
public string[] GetMyData(string fileName)
{
string[] data = GetDataFromFile(fileName);
return data;
}


protected virtual string[] GetDataFromFile(string fileName)
{
return FileUtil.ReadDataFromFile(fileName);
}
}

Then, in your unit test, derive from MyClass and call it TestableMyClass. Then you can override the GetDataFromFile method to return your own test data.

Hope that helps.

Moq cannot mock a static member of a class.

When designing code for testability it's important to avoid static members (and singletons). A design pattern that can help you refactoring your code for testability is Dependency Injection.

This means changing this:

public class Foo
{
public Foo()
{
Bar = new Bar();
}
}

to

public Foo(IBar bar)
{
Bar = bar;
}

This allows you to use a mock from your unit tests. In production you use a Dependency Injection tool like Ninject or Unity wich can wire everything together.

I wrote a blog about this some time ago. It explains which patterns an be used for better testable code. Maybe it can help you: Unit Testing, hell or heaven?

Another solution could be to use the Microsoft Fakes Framework. This is not a replacement for writing good designed testable code but it can help you out. The Fakes framework allows you to mock static members and replace them at runtime with your own custom behavior.

As mentioned in the other answers MOQ cannot mock static methods and, as a general rule, one should avoid statics where possible.

Sometimes it is not possible. One is working with legacy or 3rd party code or with even with the BCL methods that are static.

A possible solution is to wrap the static in a proxy with an interface which can be mocked

    public interface IFileProxy {
void Delete(string path);
}


public class FileProxy : IFileProxy {
public void Delete(string path) {
System.IO.File.Delete(path);
}
}


public class MyClass {


private IFileProxy _fileProxy;


public MyClass(IFileProxy fileProxy) {
_fileProxy = fileProxy;
}


public void DoSomethingAndDeleteFile(string path) {
// Do Something with file
// ...
// Delete
System.IO.File.Delete(path);
}


public void DoSomethingAndDeleteFileUsingProxy(string path) {
// Do Something with file
// ...
// Delete
_fileProxy.Delete(path);


}
}

The downside is that the ctor can become very cluttered if there are a lot of proxies (though it could be argued that if there are a lot of proxies then the class may be trying to do too much and could be refactored)

Another possibility is to have a 'static proxy' with different implementations of the interface behind it

   public static class FileServices {


static FileServices() {
Reset();
}


internal static IFileProxy FileProxy { private get; set; }


public static void Reset(){
FileProxy = new FileProxy();
}


public static void Delete(string path) {
FileProxy.Delete(path);
}


}

Our method now becomes

    public void DoSomethingAndDeleteFileUsingStaticProxy(string path) {
// Do Something with file
// ...
// Delete
FileServices.Delete(path);


}

For testing, we can set the FileProxy property to our mock. Using this style reduces the number of interfaces to be injected but makes dependencies a bit less obvious (though no more so than the original static calls I suppose).

Another option to transform the static method into a static Func or Action. For instance.

Original code:

    class Math
{
public static int Add(int x, int y)
{
return x + y;
}

You want to "mock" the Add method, but you can't. Change the above code to this:

        public static Func<int, int, int> Add = (x, y) =>
{
return x + y;
};

Existing client code doesn't have to change (maybe recompile), but source stays the same.

Now, from the unit-test, to change the behavior of the method, just reassign an in-line function to it:

    [TestMethod]
public static void MyTest()
{
Math.Add = (x, y) =>
{
return 11;
};

Put whatever logic you want in the method, or just return some hard-coded value, depending on what you're trying to do.

This may not necessarily be something you can do each time, but in practice, I found this technique works just fine.

[edit] I suggest that you add the following Cleanup code to your Unit Test class:

    [TestCleanup]
public void Cleanup()
{
typeof(Math).TypeInitializer.Invoke(null, null);
}

Add a separate line for each static class. What this does is, after the unit test is done running, it resets all the static fields back to their original value. That way other unit tests in the same project will start out with the correct defaults as opposed your mocked version.

We commonly mock instance (non-static) classes and their methods by depending on abstractions like interfaces instead of directly depending on the concrete class.

We can do the same with static methods. Here's an example of a class that depends on a static method. (This is horribly contrived.) In this example we depend directly on the static method, so we can't mock it.

public class DoesSomething
{
public long AddNumbers(int x, int y)
{
return Arithemetic.Add(x, y); // We can't mock this :(
}
}


public static class Arithemetic
{
public static long Add(int x, int y) => x + y;
}

In order to be able to mock the Add method we can inject an abstraction. Instead of injecting an interface, we can inject a Func<int, int, long> or a delegate. Either work, but I prefer a delegate because we can give it a name that says what it's for and distinguishes it from other functions with the same signature.

Here's the delegate and what the class looks like when we inject the delegate:

public delegate long AddFunction(int x, int y);


public class DoesSomething
{
private readonly AddFunction _addFunction;


public DoesSomething(AddFunction addFunction)
{
_addFunction = addFunction;
}


public long AddNumbers(int x, int y)
{
return _addFunction(x, y);
}
}

This works exactly the same way as when we inject interfaces into classes' constructors.

We can use Moq to create a mock for the delegate just like we do with interfaces.

var addFunctionMock = new Mock<AddFunction>();
addFunctionMock.Setup(_ => _(It.IsAny<int>(), It.IsAny<int>())).Returns(2);
var sut = new DoesSomething(addFunctionMock.Object);

...but that syntax is verbose and hard to read. I had to Google it. It's much easier if we use an anonymous function instead of Moq:

AddFunction addFunctionMock = (x, y) => 2;
var sut = new DoesSomething(addFunctionMock);

We can use any method that has the correct signature. If we wanted to we could define another method in our test class with that signature and use that.


As a side point, if we inject a delegate, how do we set that up with our IoC container? It looks just like registering an interface and implementation. Using IServiceCollection:

serviceCollection.AddSingleton<AddFunction>(Arithemetic.Add);

Following this advice from @manojlds:

Moq (and NMock, RhinoMock) will not help you here. You will have to create a wrapper class ( and virtual method ) around the LogException and use it in production code and test using that.

This is a partial solution I want to share.

I was having a problem similar to this one and I implemented the following solution.

The Problem

Original class with static methods

The class could be static too.

public class LogHelper
{
public static string LogError(Exception ex, string controller, string method)
{
// Code
}


public static string LogInfo(string message, string controller, string method)
{
// Code
}


public static Logger Logger(string logId, string controller, string method)
{
// Code
}
}

You cannot mock this directly, but you can do it through some interface.

The Solution

The interface

Notice the interface defines all the static methods from that class.

public interface ILogHelperWrapper
{
string LogError(Exception ex, string controller, string method);
string LogInfo(string message, string controller, string method);
Logger Logger(string logId, string controller, string method);
}

Then, implement this interface in the class wrapper.

Wrapper class

public class LogHelperWrapper : ILogHelperWrapper
{
public string LogError(Exception ex, string controller, string method)
{
return LogHelper.LogError(ex, controller, method);
}
public string LogInfo(string message, string controller, string method)
{
return LogHelper.LogInfo(message, controller, method);
}


public Logger Logger(string logId, string controller, string method)
{
return LogHelper.Logger(logId, controller, method);
}
}

That way you can mock the LogHelper's static methods.

Mocking from the unit test

  • This interface is mockeable, and the class that implements it can be used in production (though I haven't run it yet in prodution, but it worked during development).
  • Then, for example, I can call the static method LogError
public void List_ReturnList_GetViewResultWithList()
{
// Arrange
var mockLogHelper = new Mock<ILogHelperWrapper>();
    

mockLogHelper.Setup(helper => helper.LogError(new Exception(), "Request", "List")).Returns("Some Returned value");


var controller = new RequestController(mockLogHelper.Object);


// Act
var actual = controller.DisplayList();


// Assert
Assert.IsType<ViewResult>(actual);
}

Notes

As I said before, this is a partial solution, which I'm still implementing. I'm checking it as Community wiki.