模拟静态方法

问题描述 投票:65回答:7

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

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

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

怎么可能这个静态方法,StaticClass.GetFile()被嘲笑?

附:我很感激您推荐的有关Moq和单元测试的任何阅读材料。

c# unit-testing mocking moq
7个回答
37
投票

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

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

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


43
投票

@ Pure.Krome:反响很好,但我会补充一些细节

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

  • TypeMock隔离器(可以模拟一切,但它很昂贵)
  • Telerik的JustMock(新来的,更便宜,但仍然没有免费)
  • 微软的Moles(唯一免费的隔离解决方案)

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

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


16
投票

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

假设你想模拟System.DateTime.Now静态方法。以这种方式执行此操作:

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

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


6
投票

您可以使用nuget提供的Pose库来实现这一目标。它允许您模拟静态方法等。在你的测试方法中写下这个:

Shim shim = Shim.Replace(() => StaticClass.GetFile(Is.A<string>()))
    .With((string name) => /*Here return your mocked value for test*/);
var sut = new Service();
PoseContext.Isolate(() =>
    result = sut.foo("filename") /*Here the foo will take your mocked implementation of GetFile*/, shim);

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


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();
    }
}

1
投票

我喜欢Pose,但无法让它停止投掷InvalidProgramException,这似乎是一个已知的issue。现在我像这样使用Smocks

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

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

-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方法。

© www.soinside.com 2019 - 2024. All rights reserved.