Конкретный класс Moq без использования виртуального ключевого слова - PullRequest
1 голос
/ 29 апреля 2020

СИТУАЦИЯ

У меня есть следующий код здесь

ITest.cs

public interface ITest
{
    int Prop { get; }
    int Calc();
}

Test.cs

public class Test : ITest
{
    public int Prop { get; private set; }

    public int Calc()
    {
        return Prop * 2;
    }
}

И я хочу проверить метод Calc(). Если мое понимание верно, вы НЕ можете переопределить свойство getter конкретных классов без использования ключевого слова virtual.

например

var moq = new Mock<Test>();  // mocked Test class
moq.Setup(x => x.Prop).Returns(2); // throws an error, 
                                   // however with "virtual int Prop { get; }" it doesn't throw any exceptions
var moq = new Mock<ITest>();  // mocked ITest interface
moq.Setup(x => x.Prop).Returns(2); // works fine
                                   // but can not really test the Calc() method (it returns 0
                                   // because the proxy doesn't have Test's implementation)

ВОПРОС

, поэтому мой вопрос: как я могу проверить функциональность Calc(), не делая virtual int Prop

кстати ... это тоже не работает

var moq = new Mock<ITest>();
moq.Setup(x => x.Prop).Returns(2);

var obj = (Test)moq.Object; // can NOT convert ITestProxy to Test

Ответы [ 4 ]

1 голос
/ 29 апреля 2020

Моты должны использоваться в качестве замены для зависимостей вокруг того, что вы пытаетесь проверить.

Что касается вашего теста, просто протестируйте фактическое поведение вашего класса, то есть не попробуй использовать приватные методы и сеттеры. В любом случае, использование класса для проверки не является допустимым подходом.

Например:

Если вы хотите проверить класс Test:

[TestClass]
public class TestTests
{

    [TestMethod]
    public void Calc_WhenPropIsSetTo5_Returns10()
    {
        /**
         *   Seems this is what you want to test (see name of methods),
         *   and I don't think it is a good idea, as your setter is private.
         *   Since it's not a "public" behavior, you could either 
         *
         *    a) have the setter as internal for testing purposes,
         *
         *    b) (my recommendation) go for the two other tests below instead;
         *       this will actually test the exposed behavior of the class.
         *
         *    c) circumvent the accessibility (by using a library or using
         *       Reflection directly) to set the value of the prop. 
         *       See your own answer for an example. 
         */
    }

    [TestMethod]
    public void Calc_WhenCreatedWith5_Returns10()
    {
        // Arrange 
        var testSubject = new Test(5); // if the prop is set by constructor... otherwise arrange in the proper way.

        // Act
        var actualResult = testSubject.Calc();

        // Assert
        Assert.AreEqual(expected: 10, actualResult)
    }

    [TestMethod]
    public void Prop_WhenCreatedWith5_IsSetTo5()
    {
        // Arrange 
        var testSubject = new Test(5);

        // Act
        var actualResult = testSubject.Prop;

        // Assert
        Assert.AreEqual(expected: 5, actualResult)
    }
}

Если вы хотите протестировать что-то, использующее ITest.Calc(),

, то в своем фактическом коде вы должны

1 - зависеть от интерфейса ITest, а не при реализации Test

2 - иметь зависимость , внедренную в тестируемый класс, чтобы можно было заменить обычную реализацию Test на Mock<ITest>.Object()

public class ClassThatUsesTest
{
    private readonly ITest _test;

    public ClassThatUsesTest(ITest test)
    {
        _test = test;    
    }


    public int SomeFunction()
    { 
       if (_test.Calc() == 10)
       {
           return 42;
       }
       else
       {
           return 0;
       }
    }
}

[TestClass]
public class ClassThatUsesTestTests
{
    [TestMethod]
    public void SomeFunction_WhenTestCalcReturns10_Returns42()
    {
        // Arrange
        var testMock = new Mock<ITest>();
        testMock.Setup(x => x.Calc()).Returns(10);
        var testSubject = new ClassThatUsesTest(testMock.Object);
        var expectedResult = 42;

        // Act
        var actualResult = testSubject.SomeFunction();

        //
        Assert.AreEqual(expectedResult, actualResult);
    }
}

В последнем примере вы видите, что макеты помогают нам изолировать тест без зависимости от фактических деталей реализации класса Test. Даже если внезапно фактический Test сломается или изменится (например, вам нужно пройти 6 вместо 5, чтобы получить 10), метод теста не имеет значения.

1 голос
/ 29 апреля 2020

Нашел решение: Поза

Использует ILGenerator и довольно старый, НО работает.

пример

var test = new Test(); // default value of 'Prop' is  0
var shim = Shim.Replace(() => test.Prop).With((Test @this) => // idk why he needs @this param
{
    return 100; // sets the 'Prop' value to 100
});

var result = 0;
PoseContext.Isolate(() =>
{
    result = test.Calc(); // prints 200;
}, shim);

1 голос
/ 29 апреля 2020

Если вы хотите проверить Calc(), тогда тестируйте Calc(). Вам не нужно ничего издеваться

[TestMethod]
public void Test_Calc_DoublesProp()
{
    //arrange           
    var test = new Test(5);  //assumes Prop value can be injected in constructor

    //act
    int result = test.Calc();

    //assert
    Assert.IsTrue(result == 10); // 5 * 2
}
0 голосов
/ 29 апреля 2020

Хотя это кажется неправильным, поскольку оно меняет реализацию метода для целей тестирования, просто для обмена:

public class Test : ITest
{
    private int _prop;

    public int Prop
    {
        get => ((ITest)this).Prop;
        private set => _prop = value;
    }

    public int Calc()
    {
        return Prop * 2;
    }

    int ITest.Prop => _prop;
}

В тесте:

var mock = new Mock<Test>();
mock.As<ITest>()
    .Setup(x => x.Prop)
    .Returns(3);

// This should return 6
mock.Object.Calc();
...