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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12580015/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 23:46:22  来源:igfitidea点击:

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

c#unit-testingmockingmoq

提问by Himanshu

I have been doing unit testing recently and I've successfully mocked various scenarios using MOQ framework and MS Test. I know we can't test private methods but I want to know if we can mock static methods using MOQ.

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

采纳答案by Igal Tabachnik

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 Fakesin Visual Studio 2012 Ultimate /2013 /2015).

密封/静态类/方法只能与基于探查API工具伪造,像Typemock(商业)或Microsoft痣(免费,被称为假货在Visual Studio 2012旗舰版/2015分之2013)。

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 virtualmethod, like this:

FileUtil.ReadDataFromFile您可以将其包装在一个protected virtual方法中,而不是尝试 mock ,如下所示:

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 MyClassand call it TestableMyClass. Then you can override the GetDataFromFilemethod to return your own test data.

然后,在您的单元测试中,派生自MyClass并调用它TestableMyClass。然后您可以覆盖该GetDataFromFile方法以返回您自己的测试数据。

Hope that helps.

希望有帮助。

回答by Wouter de Kort

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 Ninjector Unitywich 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. 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.

另一种解决方案是使用Microsoft Fakes Framework。这不是编写良好设计的可测试代码的替代品,但它可以帮助您。Fakes 框架允许您模拟静态成员并在运行时用您自己的自定义行为替换它们。

回答by AlanT

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

正如其他答案中提到的,MOQ 不能模拟静态方法,并且作为一般规则,应该尽可能避免静态方法。

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

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

回答by zumalifeguard

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

将静态方法转换为静态 Func 或 Action 的另一种选择。例如。

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:

您想“模拟” Add 方法,但您不能。把上面的代码改成这样:

        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.

为每个静态类添加单独的一行。这样做的目的是,在单元测试运行完成后,它将所有静态字段重置为其原始值。这样,同一项目中的其他单元测试将以正确的默认值开始,而不是您的模拟版本。