简体   繁体   English

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

[英]How to mock static methods in c# using MOQ framework?

I have been doing unit testing recently and I've successfully mocked various scenarios using MOQ framework and MS Test.我最近一直在做单元测试,我已经成功地使用 MOQ 框架和 MS 测试模拟了各种场景。 I know we can't test private methods but I want to know if we can mock static methods using MOQ.我知道我们不能测试私有方法,但我想知道我们是否可以使用最小起订量模拟静态方法。

Moq (and other DynamicProxy -based mocking frameworks) are unable to mock anything that is not a virtual or abstract method. Moq(和其他基于DynamicProxy的模拟框架)无法模拟任何不是虚拟或抽象方法的东西。

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).密封/静态类/方法只能使用基于 Profiler API 的工具来伪造,例如Typemock (商业)或 Microsoft Moles(免费,在 Visual Studio 2012 Ultimate /2013 /2015 中称为Fakes )。

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.那么您不仅会有更好的设计,而且可以使用免费工具(如 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:您可以将其包装在protected virtual方法中,而不是尝试模拟FileUtil.ReadDataFromFile ,如下所示:

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 .然后,在您的单元测试中,从MyClass派生并将其命名为TestableMyClass Then you can override the GetDataFromFile method to return your own test data.然后您可以覆盖GetDataFromFile方法以返回您自己的测试数据。

Hope that helps.希望有帮助。

Another option to transform the static method into a static Func or Action.将静态方法转换为静态 Func 或 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.您想“模拟” Add 方法,但不能。 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.这样,同一项目中的其他单元测试将以正确的默认值开始,而不是您的模拟版本。

Moq cannot mock a static member of a class. Moq 不能模拟类的静态成员。

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.在生产中,您使用像NinjectUnity这样的依赖注入工具,可以将所有东西连接在一起。

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 .另一种解决方案可能是使用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. Fakes 框架允许您模拟静态成员并在运行时用您自己的自定义行为替换它们。

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.一种是使用遗留代码或第 3 方代码,甚至使用静态的 BCL 方法。

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)不利的一面是,如果有很多代理,ctor 可能会变得非常混乱(尽管可以说,如果有很多代理,那么该类可能会尝试做太多事情并且可以重构)

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.为了测试,我们可以将 FileProxy 属性设置为我们的模拟。 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).使用这种风格减少了要注入的接口数量,但使依赖关系不那么明显(尽管不比我想的原始静态调用更明显)。

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.为了能够模拟Add方法,我们可以注入一个抽象。 Instead of injecting an interface, we can inject a Func<int, int, long> or a delegate.我们可以注入Func<int, int, long>或委托,而不是注入接口。 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.我们可以使用 Moq 为委托创建模拟,就像我们使用接口一样。

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:如果我们使用匿名函数而不是 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?顺便说一句,如果我们注入一个委托,我们如何使用我们的 IoC 容器进行设置? It looks just like registering an interface and implementation.它看起来就像注册一个接口和实现。 Using IServiceCollection :使用IServiceCollection

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

Following this advice from @manojlds:遵循@manojlds 的建议

Moq (and NMock, RhinoMock) will not help you here. Moq(和 NMock、RhinoMock)不会在这里帮助你。 You will have to create a wrapper class ( and virtual method ) around the LogException and use it in production code and test using that.您将必须围绕 LogException 创建一个包装类(和虚拟方法)并在生产代码中使用它并使用它进行测试。

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.这样你就可以模拟LogHelper的静态方法。

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然后,例如,我可以调用静态方法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.我正在将其作为社区维基进行检查。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM