Vandit King
Vandit King

Reputation: 23

Moq : how can mock property and side Effect?

This is test scenario.

when kid age over 3, IsAdult == true.

I want test with Moq. But fails because Age always return 2 based on setup

How can I test this? Is mocking a bad idea?

using a debug method or brute force was not intended...

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);
    }

}

Upvotes: 1

Views: 476

Answers (1)

Nkosi
Nkosi

Reputation: 247531

Moq is unable to work with the private setter of the Age property so this makes things difficult when using Moq as it will throw exception if you try to setup the property's setter to record behaviors (side effects).

You can however avoid using Moq altogether and set the value using PrivateObject Class as a wrapper over the subject under test.

Using PrivateObject.SetProperty you can set your initial value for the test and then exercise the test as desired.

Assuming

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;
        }
    }
}

The test would look like this...

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.
}

Another option if you are able to alter the subject would be to refactor the subject to take an optional parameter in its constructor to set the initial 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;
        }
    }
}

This would then allow the test to be refactored as well to....

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.
}

Upvotes: 2

Related Questions