Reputation: 23
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
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