SvetlovA / static-mock

SMock is opensource lib for mocking static and instance methods and properties.
https://svetlova.github.io/static-mock/
MIT License
10 stars 3 forks source link

Not working, attaching an example of mocking a static method when calling from another static method #19

Closed zenyuk closed 1 year ago

zenyuk commented 1 year ago

Attached is an example (full project) with the simplest possible test for a scenario when a static method is calling another static method.

Can't make it to mock. The mock is ignored and the original method is executed instead.

StaticMockExample.zip

SvetlovA commented 1 year ago

I checked your example. You should write a test a little bit different. Part that you want to be mocked should be under callback in Mock.Setup method. I rewrote you example and it works for me:

        [Test]
        public void Test()
        {
            // arrange
            Mock.Setup(typeof(Callee), nameof(Callee.Call2), () =>
            {
                // act
                var mockActualResult = Caller.Call1(0);

                // assert
                Assert.AreEqual(11, mockActualResult);
            }).Returns(11);

            var rollbackedResult = Caller.Call1(0);

            // assert rollback
            Assert.AreEqual(2, rollbackedResult);
        }
zenyuk commented 1 year ago

thank you for the explanation, basically for my example the original test should be rewritten in the way:

[Test]
public void TestPass()
{
    // arrange
    Mock.Setup(typeof(Callee), nameof(Callee.Call2), () =>
    {
        // act
        var mockActualResult = Caller.Call1(0);

        // assert
        Assert.AreEqual(11, mockActualResult);
    }).Returns(11);
}

where I should be doing the act and assert within the Mock.Setup body only.

And if I need to mock a few calls like Callee.Call2 here executing sequentially, I assume by design I should be placing the whole test method code above inside the second Mock.Setup body. Something like

[Test]
public void TestPass()
{
    // arrange
    Mock.Setup(typeof(Callee), nameof(Callee.Call2), () =>
    {
        Mock.Setup(typeof(CalleeNext), nameof(CalleeNext.Call3), () =>
        {
            // act
            var mockActualResult = Caller.Call1(0);

            // assert
            Assert.AreEqual(26, mockActualResult);
        }).Returns(15);
    }).Returns(11);
}

// while the method under test is:

    public class Caller
    {
        public static int Call1(int x)
        {
            x++;
            int r = Callee.Call2(x);
            r += CalleeNext.Call3(x);
            return r;
        }
    }
SvetlovA commented 1 year ago

Yes, your examples look good and should work. If not, let me know. And this is a good idea to support multiple classes/methods mocking in one setup. I will try to implement this in future versions. Thank you.

zenyuk commented 1 year ago

What would be good is to support sequential calls instead of hierarchical

existing implementation:

    // arrange
    Mock.Setup(typeof(Callee), nameof(Callee.Call2), () =>
    {
        Mock.Setup(typeof(CalleeNext), nameof(CalleeNext.Call3), () =>
        {
            // act
            var mockActualResult = Caller.Call1(0);

            // assert
            Assert.AreEqual(26, mockActualResult);

        }).Returns(15);
    }).Returns(11);

to be like:

    // arrange
    Mock.Setup(typeof(Callee), nameof(Callee.Call2)).Returns(11);
    Mock.Setup(typeof(CalleeNext), nameof(CalleeNext.Call3)).Returns(15);

    // act
    var mockActualResult = Caller.Call1(0);

    // assert
    Assert.AreEqual(26, mockActualResult); 
SvetlovA commented 1 year ago

Thank you for suggestions. Looks like good improvement. I will implement this, but also for compatibility I will leave hierarchical calls too.

zenyuk commented 1 year ago

looks awesome, thanks for your work

Only what I found is if I change the mocked method to be generic, it fails. I am using StaticMock.Mock.Setup(Type type, String methodName) signature

    public class Callee
    {
        public static int Call2<T>(T x)
        {
            return 11;
        }
    }

stack trace:

System.ArgumentNullException: Value cannot be null.
Parameter name: GenericTypes

   at StaticMock.Helpers.SetupMockHelper.GetOriginalMethodInfo(Type type, String methodName, SetupProperties setupProperties)

   at StaticMock.Helpers.SetupMockHelper.SetupInternal(Type type, String methodName, Action action, SetupProperties setupProperties)

   at StaticMock.Mock.Setup(Type type, String methodName)
SvetlovA commented 1 year ago

for generic methods mock you should close generic type with SetupProperties. Here is an example: Method for test:

public static TEntity GenericTestMethodReturnDefaultWithoutParameters<TEntity>()
{
    return default;
}

Mock:

const int expectedResult = 2;

using var _ = Mock.Setup(
    typeof(TestStaticClass),
    nameof(TestStaticClass.GenericTestMethodReturnDefaultWithoutParameters),
    new SetupProperties { GenericTypes = new[] { typeof(int) } })
.Returns(expectedResult);

var actualResult = TestStaticClass.GenericTestMethodReturnDefaultWithoutParameters<int>();
Assert.AreEqual(expectedResult, actualResult);

Here a link for example in tests https://github.com/SvetlovA/static-mock/blob/c9f089d14e0db3b5a52f62b3a79423e7fc05a6da/src/StaticMock.Tests/Tests/Sequential/ReturnsTests/MockReturnsTests.cs#L330