I have recently encountered multiple articles with title Everytime a mock returns a mock a fairy dies And I ran into exact same situation while using factory class in my code. I am writing a sample Java code here to explain in detail what I mean.
@RequiredArgsConstructor
class Battle{
private final GunFactory;
public Damage attack(Bullet availableBullet, Enemy enemy){
Gun gun = gunFactory.create(availableBullet);
return gun.shoot(enemy);
}
}
class BattleTest{
private GunFactory gunFactory = mock(GunFactory.class);
private Gun gun = mock(Gun.class);
private Battle battle = new Battle(gunFactory);
@Test
public void attack(){
Enemy anyEnemy = new Enemy(strength=10);
Bullet anyBullet = new Bullet("Machine gun bullet");
Damage anyDamage = new Damage(100);
when(gunFactory.create(anyAvailableBullet)).thenReturn(gun);
when(gun.shoot(anyEnemy)).thenReturn(anyDamage);
assertThat(battle.attack(anyBullte, anyEnemy), is(anyDamage));
}
}
As you can see in the sample (ugly) code my factory is a mock and is returning another mock. So it got me wondering if there is anyway to avoid this. I tried moving one class higher, so that the GunFactory
sends Gun
object as a parameter to attack
function, but I would still run into same issue when I test the other class.
Is there a way to avoid this. Or with factory pattern is this inevitable?
1 Answer 1
I can think of a number of ways:
- You can make your factory use a dictionary of injected types. Then you can use the real factory and inject a mock
- You can flatten your OOP code so that you don't have any child objects.
- You can stop mocking the child objects and concentrate on mocking only things that persist state
I think 3 is your best option here. You could argue that a test of battle which uses a real GunFactory
and Gun
is not a unit test, but all you really need to mock for a good test is the data.
So for example say gun factory reads the gun data from a database, that's the only thing you need to mock to have repeatable fast tests. You can leave the discussion about whether its a 'real unit test' to the philosophers
-
Your answer is a good reminder that unit tests need not to be "pure" to be useful!T. Sar– T. Sar2020年03月05日 18:07:03 +00:00Commented Mar 5, 2020 at 18:07
-
1But the more complex they get, you might have to rethink portions of your design. A unit test exposes everything a person needs to keep straight in their brain when they read your code.Berin Loritsch– Berin Loritsch2020年03月06日 14:15:44 +00:00Commented Mar 6, 2020 at 14:15
-
@BerinLoritsch yes! I’d also say that unit tests help make your code readable.candied_orange– candied_orange2020年03月07日 04:25:54 +00:00Commented Mar 7, 2020 at 4:25
Battle.attack
, when it should really relate the expectedDamage
result from theBullet
andEnemy
arguments.