Learning Unit Testing X – Finishing Hiding

Last time, I got the tests for Charging out of the way, and got through a raft of simple checks for Hiding. This time, a few of the more complicated checks.

First off, a model can move around while hiding as long as it remains out of sight. I’m not sure how to be checking every point along the move, but I can check at the end of the movement whether it’s still out of sight.

[TestMethod]
public void Hide_ThrowsExceptionIfInSightAfterMove()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
bool exceptionThrown = false;
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Hide();
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(1);
Assert.IsTrue(testModel.IsHiding);
try
{
testModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
exceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsTrue(exceptionThrown);
Assert.IsFalse(testModel.IsHiding);
}

The best way to check this is probably to call the Hide method at the end of MoveModel – any change in the model’s visibility will be picked up. Code re-use: excellent!

if (this.IsHiding)
{
this.Hide();
}

But I still don’t get a pass… I also need to set IsHiding to false in the Hide method, when it throws an exception. Then the test goes green.

The next test is that after an enemy model has moved, it might force a model to stop being hidden. I will keep the current action of throwing an exception – this will let the imaginary UI show that hiding has ceased.

[TestMethod]
public void Hide_StopsHidingIfInSightAfterEnemyMove()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Movement = 4;
bool exceptionThrown = false;
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Hide();
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(1);
Assert.IsTrue(testModel.IsHiding);
try
{
enemyModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
exceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsTrue(exceptionThrown);
Assert.IsFalse(testModel.IsHiding);
}

The code to pass this test should be just adding the following check to the end of MoveModel (as long as Hide is added to the IModel interface).

foreach (IModel enemyModel in (from models in _gameManager.Models
where models.Player != this.Player
select models))
{
enemyModel.Hide();
}

But this doesn’t work – not only does it not work, but it breaks other tests! Now, Charge_SetsIsChargingFlag, MoveModel_CannotMoveFurtherThanModelsMovement and NewTurn_SetsIsChargingToFalse throw exceptions and this test fails on the penultimate assert that the exception was thrown. I believe the error with this test is that the Mock GameManager I’m using is only returning the enemyModel – not the testModel. When I added the testModel to the setup, this test at least passes. The other tests all throw exceptions. This is probably due to the fact that the GameManager is not being correctly set up in each case, to return an empty List<IModel> – as a mock object, it returns null instead and the above code to check for other models fails when passed a null.

By adding in the required setup line to each of those tests, I get 31 passes again.

Finally, a model is automatically forced out of hiding if an enemy model is within it’s Initiative range of the hiding model. This one needs to be checked when the model chooses to hide, and also whenever it moves or the enemy model moves. Since all three of these cause the Hide method to be called, that should probably be where it sits. The following tests should cover off those three situations:

[TestMethod]
public void Hide_CannotHideIfEnemyInInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Location = new LocationPoint(1, 0, 0);
enemyModel.Initiative = 2;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
bool correctExceptionThrown = false;
try
{
testModel.Hide();
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_StopHidingIfModelMovesIntoEnemyInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Location = new LocationPoint(5, 0, 0);
enemyModel.Initiative = 3;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
testModel.IsHiding = true;
testModel.Movement = 4;
bool correctExceptionThrown = false;
try
{
testModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_StopHidingIfEnemyMovesWithinInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(5, 0, 0);
enemyModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Initiative = 3;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
testModel.IsHiding = true;
enemyModel.Movement = 4;
bool correctExceptionThrown = false;
try
{
enemyModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}

The passing code sits in Hide, and is very pleasantly simple. Hide becomes very long:

public void Hide()
{
if (this.IsCharging || this.IsRunning)
{
HidingException ex = new HidingException("Cannot hide if charged or ran this turn.");
throw ex;
}
else
{
List<IModel> enemyModels = (from models in this._gameManager.Models
where models.Player != this.Player
&& (this._objectManager.GetLineOfSight(models, this) > 0.6 || this.GetDistanceFrom(models.Location.X, models.Location.Y, models.Location.Z) <= models.Initiative)
select models).ToList();
if (enemyModels.Count > 0)
{
this.IsHiding = false;
HidingException ex = new HidingException("Cannot hide in sight or Initiative range of enemy models.");
ex.EnemyModels = enemyModels;
throw ex;
}
else
{
this.IsHiding = true;
}
}
}

But this breaks several other tests. The reason is that some of the previously written tests are not setting locations for the enemy and test models, and the distance between them (zero) is always equal to or less than the default Initiative value (also zero). Changing the tests to give them a bit of space fixes this problem.

Since a model can remain hidden for several successive turns, it doesn’t need to reset the IsHiding flag – Hiding for all hidden models is checked whenever a model is moved, so it will be taken out of hiding when a player wishes or when it is chosen.

Next time, I move off of page 11, but I’m not sure where to yet…

Leave a comment

Your email address will not be published. Required fields are marked *