Moq:如何模拟属性和副作用?

时间:2017-11-26 02:39:47

标签: c# unit-testing testing mocking moq

这是测试场景。

当孩子年龄超过3岁时,IsAdult == true

我想用Moq进行测试。但失败是因为Age总是根据设置

返回2

我该如何测试?嘲笑一个坏主意?

使用调试方法或蛮力不是......

public class Kid
{
    public virtual float Age { get; private set; } = 0;
    public bool IsAdult { get; private set; }
    private float _ageIncreasePerHour = 1;

    public void OnHourPass()
    {
        Age += _ageIncreasePerHour;

        if (Age >= 3)
        {
            IsAdult = true;
        }
    }

    public void Debug_SetAge(float newAge)
    {
        Age = newAge;
    }
}

public class Test_Kid
{
    //fail. how can i mocking?
    [Test]
    public void WhenAgeOver3_Kid_IsAdult_UseMocking()
    {
        var mockKid = new Mock<Kid> { CallBase = true };
        mockKid.Setup(x => x.Age).Returns(2);
        Assert.AreEqual(2, mockKid.Object.Age);
        mockKid.Object.OnHourPass();
        Assert.AreEqual(3, mockKid.Object.Age); // but 2.
        Assert.AreEqual(true, mockKid.Object.IsAdult); // but false.
    }

    //success. but not intended. Debug-Method is anti pattern!
    [Test]
    public void WhenAgeOver3_Kid_IsAdult_UseDebugMethod()
    {
        var newKid = new Kid();
        newKid.Debug_SetAge(2);
        newKid.OnHourPass();
        Assert.AreEqual(true, newKid.IsAdult);
    }

}

1 个答案:

答案 0 :(得分:1)

Moq无法使用Age属性的私有 setter,因此在使用Moq时会遇到困难,因为如果您尝试将属性的setter设置为记录,它会抛出异常行为(副作用)。

但是,您可以完全避免使用Moq,并使用PrivateObject Class作为被测对象的包装来设置值。

使用PrivateObject.SetProperty,您可以设置测试的初始值,然后根据需要进行测试。

假设

public class Kid {
    public virtual float Age { get; private set; }
    public bool IsAdult { get; private set; }
    private float _ageIncreasePerHour = 1;

    public void OnHourPass() {
        Age += _ageIncreasePerHour;
        if (Age >= 3) {
            IsAdult = true;
        }
    }
}

测试看起来像这样......

public void _WhenAgeOver3_Kid_IsAdult() {
    //Arrange
    var initialAge = 2;
    var expectedAge = initialAge + 1;
    var kid = new Kid();
    var wrapper = new PrivateObject(kid);            
    wrapper.SetProperty("Age", initialAge);
    Assert.AreEqual(initialAge, kid.Age);

    //Act
    kid.OnHourPass();

    //Assert
    Assert.AreEqual(expectedAge, kid.Age); // should be 3.
    Assert.AreEqual(true, kid.IsAdult); // should be true.
}

如果您能够改变主题,另一个选择是重构主题以在其构造函数中采用可选参数来设置初始Age

public class Kid {

    public Kid(int initialAge = 0) {
        Age = initialAge;
    }

    public virtual float Age { get; private set; }
    public bool IsAdult { get; private set; }
    private float _ageIncreasePerHour = 1;

    public void OnHourPass() {
        Age += _ageIncreasePerHour;
        if (Age >= 3) {
            IsAdult = true;
        }
    }
}

这样就可以将测试重构为....

public void _WhenAgeOver3_Kid_IsAdult() {
    //Arrange
    var initialAge = 2;
    var expectedAge = initialAge + 1;
    var kid = new Kid(initialAge);
    Assert.AreEqual(initialAge, kid.Age);

    //Act
    kid.OnHourPass();

    //Assert
    Assert.AreEqual(expectedAge, kid.Age); // should be 3.
    Assert.AreEqual(true, kid.IsAdult); // should be true.
}