模拟静态方法

时间:2011-05-03 01:12:19

标签: c# unit-testing mocking moq

最近,我开始使用Moq进行单元测试。我使用Moq来模拟我不需要测试的类。

您通常如何处理静态方法?

public void foo(string filePath)
{
    File f = StaticClass.GetFile(filePath);
}

这个静态方法StaticClass.GetFile()如何被模拟?

P.S。我很感激你推荐的关于Moq和单元测试的任何阅读材料。

8 个答案:

答案 0 :(得分:43)

@ Pure.Krome:反应良好,但我会添加一些细节

@Kevin:您必须根据可以为代码带来的变化选择解决方案 如果你可以改变它,一些依赖注入使代码更容易测试。 如果你做不到,你需要一个良好的隔离 使用免费的模拟框架(Moq,RhinoMocks,NMock ......),您只能模拟委托,接口和虚拟方法。因此,对于静态,密封和非虚拟方法,您有3种解决方案:

  • TypeMock Isolator (可以模拟所有内容,但价格昂贵)
  • JustMock Telerik (新来的,更便宜,但仍然没有 免费)
  • Moles Microsoft (唯一免费的隔离解决方案)

我推荐Moles ,因为它是免费的,高效的并且使用像Moq这样的lambda表达式。只有一个重要细节:Moles提供存根,而不是模拟。所以你仍然可以使用Moq作为接口和代理;)

  

Mock:一个实现接口的类,允许动态设置值以返回/异常从特定方法抛出,并提供检查是否已调用特定方法的能力调用。
  存根:与模拟类类似,不同之处在于它无法验证方法是否已被调用/未被调用。

答案 1 :(得分:37)

  

像Moq或Rhinomocks这样的模拟框架只能创建对象的模拟实例,这意味着无法模拟静态方法。

您还可以search Google了解更多信息。

此外,之前在StackOverflow hereherehere上提出了一些问题。

答案 2 :(得分:16)

.NET中有可能排除MOQ和任何其他模拟库。您必须右键单击包含要模拟的静态方法的程序集上的解决方案资源管理器,然后选择添加伪装配。接下来,您可以自由地模拟该程序集的静态方法。

假设您要模拟System.DateTime.Now静态方法。以这种方式做到这一点:

using (ShimsContext.Create())
{
    System.Fakes.ShimDateTime.NowGet = () => new DateTime(1837, 1, 1);
    Assert.AreEqual(DateTime.Now.Year, 1837);
}

每个静态属性和方法都有类似的属性。

答案 3 :(得分:6)

您可以使用nuget提供的Pose库来实现此目的。它使您可以模拟静态方法。在您的测试方法中编写以下内容:

L1 = sorted(L1, key=keyfunc)
L2 = [list(g) for _, g in groupby(L1, keyfunc))

如需进一步阅读,请参见https://medium.com/@tonerdo/unit-testing-datetime-now-in-c-without-using-interfaces-978d372478e8

答案 4 :(得分:1)

我一直在玩一个重构静态方法来调用委托的概念,你可以在外面设置它来进行测试。

这不会使用任何测试框架,并且将是一个完全定制的解决方案,但重构不会影响您的调用者的签名,因此它将是相对安全的。

要使其工作,您需要访问静态方法,因此它不适用于任何外部库,例如System.DateTime

下面是一个我一直在玩的例子,我已经创建了几个静态方法,一个带有两个参数的返回类型和一个没有返回类型的泛型。

主要的静态类:

public static class LegacyStaticClass
{
    // A static constructor sets up all the delegates so production keeps working as usual
    static LegacyStaticClass()
    {
        ResetDelegates();
    }

    public static void ResetDelegates()
    {
        // All the logic that used to be in the body of the static method goes into the delegates instead.
        ThrowMeDelegate = input => throw input;
        SumDelegate = (a, b) => a + b;
    }

    public static Action<Exception> ThrowMeDelegate;
    public static Func<int, int, int> SumDelegate;

    public static void ThrowMe<TException>() where TException : Exception, new()
        => ThrowMeDelegate(new TException());

    public static int Sum(int a, int b)
        => SumDelegate(a, b);
}

单元测试(xUnit和应该)

public class Class1Tests : IDisposable
{
    [Fact]
    public void ThrowMe_NoMocking_Throws()
    {
        Should.Throw<Exception>(() => LegacyStaticClass.ThrowMe<Exception>());
    }

    [Fact]
    public void ThrowMe_EmptyMocking_DoesNotThrow()
    {
        LegacyStaticClass.ThrowMeDelegate = input => { };

        LegacyStaticClass.ThrowMe<Exception>();

        true.ShouldBeTrue();
    }

    [Fact]
    public void Sum_NoMocking_AddsValues()
    {
        LegacyStaticClass.Sum(5, 6).ShouldBe(11);
    }

    [Fact]
    public void Sum_MockingReturnValue_ReturnsMockedValue()
    {
        LegacyStaticClass.SumDelegate = (a, b) => 6;
        LegacyStaticClass.Sum(5, 6).ShouldBe(6);
    }

    public void Dispose()
    {
        LegacyStaticClass.ResetDelegates();
    }
}

答案 5 :(得分:1)

我喜欢Pose,但无法阻止它抛出似乎是已知的issue的InvalidProgramException。现在,我像这样使用Smocks

Smock.Run(context =>
{
    context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

    // Outputs "2000"
    Console.WriteLine(DateTime.Now.Year);
});

答案 6 :(得分:0)

使用 Microsoft Fakes:

Add the fakes assembly,那么如果你有这个静态方法......

//code under test
public static class MyClass {
    public static int MyMethod() {
        ...
    }
}

...你可以像这样模拟它:

// unit test code
using (ShimsContext.Create())
{
    ShimMyClass.MyMethod = () => 5;
}

来源:https://docs.microsoft.com/en-us/visualstudio/test/using-shims-to-isolate-your-application-from-other-assemblies-for-unit-testing?view=vs-2019#static-methods

答案 7 :(得分:-1)

我知道这有点晚了但是这个迂回的解决方案允许我使用Moq模拟一个静态方法。

为此,我创建了一个类(让我们称之为Placeholder),其中一个方法称为静态方法StaticClass.GetFile

public class Placeholder{  

    //some empty constructor

    public File GetFile(){

        File f = StaticClass.GetFile(filePath);
        return f;
    }
}

然后,我没有在StaticClass.GetFile中调用foo,而是创建了Placeholder的实例,并调用了GetFile函数。

public void foo(string filePath)
{
    Placeholder p = new Placeholder();
    File f = p.GetFile(filePath);
}

现在,在单元测试中,我能够模拟StaticClass.GetFile类中的非静态GetFile方法,而不是尝试模拟Placeholder