c# - 用Moq模擬延伸方法




unit-testing mocking extension-methods (5)

我有一個預先存在的接口...

public interface ISomeInterface
{
    void SomeMethod();
}

我用mixin擴展了這個內置...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}

我有一個叫做這個我想測試的類。

public class Caller
{
    private readonly ISomeInterface someInterface;

    public Caller(ISomeInterface someInterface)
    {
        this.someInterface = someInterface;
    }

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}

和一個測試,我想嘲笑接口並驗證對擴展方法的調用...

    [Test]
    public void Main_BasicCall_CallsAnotherMethod()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();
        someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable();

        var caller = new Caller(someInterfaceMock.Object);

        // Act
        caller.Main();

        // Assert
        someInterfaceMock.Verify();
    }

然而運行這個測試會產生一個異常...

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()

我的問題是,是否有一種很好的方式來模擬mixin調用?


Answers

我發現我不得不發現我試圖嘲笑輸入的擴展方法的內部,並模擬擴展中發生的事情。

我使用擴展名查看直接添加代碼到您的方法。 這意味著我不需要模擬擴展內部發生的情況而不是擴展本身。


我用一個包裝來解決這個問題。 創建一個包裝對象並傳遞你的模擬方法。

參見Paul Irwin的單元測試嘲弄靜態方法 ,它有很好的例子。


當我包裝對象本身時,我喜歡使用包裝器(適配器模式)。 我不確定我會用它來包裝一個擴展方法,它不是對象的一部分。

我使用了Action,Func,Predicate或delegate類型的內部惰性可注入屬性,並允許在單元測試期間注入(交換)該方法。

    internal Func<IMyObject, string, object> DoWorkMethod
    {
        [ExcludeFromCodeCoverage]
        get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
        set { _DoWorkMethod = value; }
    } private Func<IMyObject, string, object> _DoWorkMethod;

然後你調用Func而不是實際的方法。

    public object SomeFunction()
    {
        var val = "doesn't matter for this example";
        return DoWorkMethod.Invoke(MyObjectProperty, val);
    }

有關更完整的示例,請查看http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/


你不能“直接”嘲笑靜態方法(因此擴展方法)與嘲笑框架。 您可以嘗試使用Moles免費工具( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ),該工具實現了不同的方法。 以下是該工具的說明:

Moles是基於代表的.NET中測試存根和繞行的輕量級框架。

痣可以用來繞開任何.NET方法,包括密封類型的非虛擬/靜態方法。

你可以在任何測試框架中使用Moles(它是獨立的)。


Below is an extension method that adapts Rick Strahl's code (and the comments too) to stop you having to guess or read the byte order mark of a byte array or text file each time you convert it to a string.

The snippet allows you to simply do:

byte[] buffer = File.ReadAllBytes(@"C:\file.txt");
string content = buffer.GetString();

If you find any bugs please add to the comments. Feel free to include it in the Codeplex project.

public static class Extensions
{
    /// <summary>
    /// Converts a byte array to a string, using its byte order mark to convert it to the right encoding.
    /// Original article: http://www.west-wind.com/WebLog/posts/197245.aspx
    /// </summary>
    /// <param name="buffer">An array of bytes to convert</param>
    /// <returns>The byte as a string.</returns>
    public static string GetString(this byte[] buffer)
    {
        if (buffer == null || buffer.Length == 0)
            return "";

        // Ansi as default
        Encoding encoding = Encoding.Default;       

        /*
            EF BB BF    UTF-8 
            FF FE UTF-16    little endian 
            FE FF UTF-16    big endian 
            FF FE 00 00 UTF-32, little endian 
            00 00 FE FF UTF-32, big-endian 
         */

        if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
            encoding = Encoding.UTF8;
        else if (buffer[0] == 0xfe && buffer[1] == 0xff)
            encoding = Encoding.Unicode;
        else if (buffer[0] == 0xfe && buffer[1] == 0xff)
            encoding = Encoding.BigEndianUnicode; // utf-16be
        else if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
            encoding = Encoding.UTF32;
        else if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
            encoding = Encoding.UTF7;

        using (MemoryStream stream = new MemoryStream())
        {
            stream.Write(buffer, 0, buffer.Length);
            stream.Seek(0, SeekOrigin.Begin);
            using (StreamReader reader = new StreamReader(stream, encoding))
            {
                return reader.ReadToEnd();
            }
        }
    }
}




c# unit-testing mocking extension-methods moq