C# Unittesting mit Moq

Rossibaer

Lieutenant
Registriert
Apr. 2007
Beiträge
754
Hallo zusammen,

ich bin gerade dabei das Moq Framework etwas näher anzuschauen. Dabei fällt mir auf das ich momentan Probleme habe Methoden zu mocken, die ref und out Parameter haben. Mein Lösungsansatz geht dabei in die Untiefen von Reflection. Kennt jemand eine andere Lösung um ref und out Parameter in Methoden zu mocken? Zur Zeit verwende ich Moq v4.5.28.0.

Bisher habe ich folgende Lösung basierend auf Assigning out/ref parameters in Moq (StackOverflow):

Code:
public static class MockExtensions
    {
        public static IReturnsThrows<TMock, TReturn> Returns<TMock, TReturn>(this ICallback<TMock, TReturn> mock, Delegate action)
            where TMock : class
        {
            return SetReturns(mock, action);
        }

        public static ICallbackResult Callback(this ICallback mock, Delegate action)
        {
            return SetCallback(mock, action);
        }

        private static IReturnsThrows<TMock, TReturn> SetReturns<TMock, TReturn>(ICallback<TMock, TReturn> mock, object action)
            where TMock : class
        {
            var genericMethodCallReturn = mock
                .GetType()
                .Assembly
                .GetTypes()
                .First(t => t.FullName.StartsWith("Moq.MethodCallReturn", StringComparison.InvariantCultureIgnoreCase) && t.IsGenericType);
            var constructedMethodCallReturn = genericMethodCallReturn.MakeGenericType(typeof(TMock), typeof(TReturn));
            constructedMethodCallReturn.InvokeMember("SetReturnDelegate", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock, new[] { action });
            return (IReturnsThrows<TMock, TReturn>) mock;
        }

        private static ICallbackResult SetCallback(ICallback mock, object action)
        {
            var methodCall = mock
                .GetType()
                .Assembly.GetType("Moq.MethodCall");
            methodCall.InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock, new[] { action });
            return (ICallbackResult)mock;
        }
    }

mein Interface sehe so aus:

Code:
        public interface IMyInterface
        {
            bool Check(out Type targetType, ref string propertyName);
            void Apply(out Type targetType, ref string propertyName);
        }

im Unittest würde ich dann folgendes machen:

Code:
        public delegate bool CheckDelegate(out Type targetType, ref string propertyName);
        public delegate void ApplyDelegate(out Type targetType, ref string propertyName);

        [TestMethod]
        public void MyTest1()
        {
            var targetType1 = It.IsAny<Type>();
            var propertyName1 = It.IsAny<string>();
            var targetType2 = It.IsAny<Type>();
            var propertyName2 = It.IsAny<string>();

            var mock = new Mock<IMyInterface>();
            mock
                .Setup(obj => obj.Check(out targetType1, ref propertyName1))
                .Returns((CheckDelegate)((out Type t, ref string p) =>
                {
                    t = typeof(Guid);
                    p = Guid.NewGuid().ToString();
                    return true;
                }));

            mock
                .Setup(obj => obj.Apply(out targetType2, ref propertyName2))
                .Callback((ApplyDelegate)((out Type t, ref string p) =>
                {
                    t = typeof(string);
                    p = Guid.NewGuid().ToString();
                }));

            var sut = mock.Object;

            if (sut.Check(out targetType1, ref propertyName1))
                sut.Apply(out targetType2, ref propertyName2);

            Assert.AreNotEqual(typeof(object), targetType1);
            Assert.AreNotEqual(Guid.Empty.ToString(), propertyName1);
            Assert.AreNotEqual(typeof(object), targetType2);
            Assert.AreNotEqual(Guid.Empty.ToString(), propertyName2);
        }

Gibt es eine Möglichkeit Delegates mit ref/out Parametern ohne Reflection zu verwenden?

Viele Grüße
Rossibaer
 
Ich glaube nicht, alle Lösungen die man so googlen kann sehen ähnlich wie deine aus.

Probiers doch mal mit FakeItEasy, das unterstützt out und ref von Haus aus.
Soll sowieso besser als Moq sein, ich wollte es selber mal beim nächsten Projekt austesten.

https://fakeiteasy.github.io/
 
Hi gunters,

FakeItEasy klingt interessant.
Bis jetzt kannte ich es noch nicht...
Cool wäre es, wenn FakeItEasy auch das faken von statischen Methodenaufrufen wie z.B. DateTime.Now unterstützen könnte. Moq kann dies nicht. Aber wenn ich mir die Doku von FakeItEasy so ansehe, dann wird das wahrscheinlich von FakeItEasy auch nicht unterstützt. Ich glaube das schaffen nur die teuren "Bezahlversionen" abseits von OpenSource...

Vielen Dank für deine Antwort.

Viele Grüße
Rossibaer
 
Ja, für statische Aufrufe gabs mal Moles von Microsoft, das war frei. Das konnte z.B. DateTime.Now faken.

Der Nachfolger heißt jetzt Microsoft Fakes. Hab es noch nicht benutzt, kann auch sein dass das nur mit der Enterprise Version von VS Studio geht.

Aber Moles war eh nicht so toll, da es einen extra compile-step benötigt hat (weiß nicht ob das bei MS Fakes auch so ist).

Wie auch immer, wenn ich statische Methoden in eigenen Projekten benutzen muss, mache ich da immer einen Wrapper wie z.B. einen TimeProvider mit zugehörigem Interface, so dass ich die Abhängigkeit einfach mocken kann. Der Rest des Projekts bekommt dann einfach das Interface injected.
 
Hi Gunters,
irgendwie ist mir entgangen, dass du nochmal hier geantwortet hattest. Sorry für meine späte Antwort.

Also zur Zeit entwickel ich mein Programm strikt mit Interfaces, d.h. alle Services usw. werden nur noch über ihr Interface statt direkt angesprochen. Soweit so gut, diese lassen sich exzellent mocken. Nur leider werde ich nicht drum herum kommen auch Teile des Frameworks mal mocken zu müssen. DateTime.Now wäre da so ein klassisches Beispiel. Über einen Wrapper wäre das sicher so eine Möglichkeit. Nur habe ich da ein Problem mit, denn ich entwickel dann Code dessen einzige Daseinsberechtigung die Testbarkeit wäre, was man eher vermeiden sollte, wenn ich so der gängigen Literatur folge (z.B. Stichwort KISS Prinzip). Desweiteren habe ich auch die Situation, dass hin und wieder Probleme auftreten die ich z.B. über einen Wrapper nicht so richtig in den Griff bekomme. Vielleicht liegt es auch an meinem wenigen Verständnis von der Materie oder der Tatsache, dass ich mir das Ganze erst erarbeite. Wie dem auch sei...

Falls noch jemand ein möglichst kostenloses/opensource Framework kennt, dass
... statische, sealed, non-virtual Methoden mocken kann
... Methoden mit ref, out Parametern mocken kann
wäre ich sehr dankbar, wenn er das mal hier mitteilen könnte.

Ansonsten vielen Dank für die Teilnahme vor allem an dich Gunters. Du hast mir schon ein paar Hinweise gegeben, wo ich mal noch nachforschen kann...

Grüße
Rossibaer
 
Zurück
Oben