Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asserting a call to a public method on the same mock instance

I have the following test

[Test]
public void Attack_TargetWith3Damage_CausesAttackerToDeal3DamageToTarget()
{
    var realAttacker = CreateCreature(damage: 3);
    var wrappedAttacker = A.Fake<ICreature>(x => x.Wrapping(realAttacker));
    var target = A.Fake<ICreature>();
    wrappedAttacker.Attack(target);
    A.CallTo(() => wrappedAttacker.DealDamage(target, 3)).MustHaveHappened();
}

The problem is that the call to DealDamage from the Attack method isn't being registered, because inside the method, this is realAttacker not wrappedAttacker attacker hence the method call isn't being intercepted.

How can I test this assertion? Can this be done with FakeItEasy? Does a different mocking framework allow me to test this?

like image 444
Davy8 Avatar asked Oct 10 '22 06:10

Davy8


2 Answers

You can get pretty close to what you are after using Moq for your mocking framework.

Take this as an example:

public interface ICreature { ... }

public class Creature : ICreature
{
    ... 

    public void Attack(ICreature creature)
    {
        DealDamage(creature, 3); // Hard-coded 3 to simplify example only
    }

    public virtual void DealDamage(ICreature target, int damage) { ... }
}

.... Test ....
var wrappedAttacker = new Mock<Creature>();
var mockTarget = new Mock<ICreature>();

wrappedAttacker.Object.Attack(mockTarget.Object);

wrappedAttacker.Verify(x => x.DealDamage(mockTarget.Object, 3), Times.Once());

In this case I'm "wrapping" the a Creature instance in a mock for the attacker role and creating a ICreature mock for the target role. I then call the Attack method from the attacker; verifying that the same attacker's DealDamage was called (with the correct target and 3 damage), exactly one time.

What makes this verification possible in Moq is that the DealDamage function is marked virtual. This may be a deal-breaker for your situation, but it does address the "Does a different mocking framework allow me to test this?" question.

like image 147
ckittel Avatar answered Oct 12 '22 20:10

ckittel


Thanks to @ckittel for pointing me to this answer. For this to work, the Creature class needs to have a parameterless constructor, and the methods need to be virtual.

The one extra thing with FakeItEasy seems that you have to tell it to call base method, otherwise conceptually it's the same, just different syntax.

[Test]
public void Attack_TargetWith3Damage_CausesAttackerToDeal3DamageToTarget()
{
    var attacker = A.Fake<Creature>();
    A.CallTo(attacker).CallsBaseMethod(); //Otherwise it seems all calls are no-ops.
    attacker.Stats.Damage = 3;

    var target = A.Fake<ICreature>();
    attacker.Attack(target);
    A.CallTo(() => attacker.DealDamage(target, 3)).MustHaveHappened();
}
like image 33
Davy8 Avatar answered Oct 12 '22 19:10

Davy8