mobile menu icon

Polymorphic test setup with template method

Published by Carlos Blé on 21/12/2015

C#, .Net, Refactoring, Design Patterns, Testing, Contract Testing, Role Testing


We had a kind of duplication in our tests that we didn’t know how to deal with. The refactoring Introduce Polymorphic Creation with Factory Method explained by Joshua Kerievsky in his brilliant book “Refactoring to Patterns” gave us the solution to avoid duplicated tests.

[TestFixture] public class
ChangingColorWithImplicitExclusionsShould : ConfigurationTests {
[Test] public void
not_allow_change_when_its_compulsory_has_the_same_family_than_a_configured_equipment() {
CatalogBuilder
.AddEquipmentWithFamily("PR1", "Radio")
.AddEquipmentWithFamily("PR2", "Radio")
.AddColor("red", WithEquipmentsCompulsory("PR2"));
var configuration = Agiven.ModelConfiguration()
.With(Agiven.ConfigEquipment("PR1"))
.Build();
Expect.CallTo(() => ExecuteChangeColor("red", configuration))
.ToThrow<EquipmentsWithSameFamilyException>();
}
}
[TestFixture] public class
ChangingInteriorWithImplicitExclusionsShould : ConfigurationTests {
[Test] public void
not_allow_change_when_its_compulsory_has_the_same_family_than_a_configured_equipment() {
CatalogBuilder
.AddEquipmentWithFamily("PR1", "Radio")
.AddEquipmentWithFamily("PR2", "Radio")
.AddInterior("xyz", WithEquipmentsCompulsory("PR2"));
var configuration = Agiven.ModelConfiguration()
.With(Agiven.ConfigEquipment("PR1"))
.Build();
Expect.CallTo(() => ExecuteChangeInterior("xyz", configuration))
.ToThrow<EquipmentsWithSameFamilyException>();
}
}

These tests are very similar, the differences are in lines 8 and 25, and also in lines 13 and 30. The first test tries to change the color of a configuration, whereas the second one tries to change the interior. Part of the handling business logic is the same. This is just one scenario but we had many others with the same expected behavior for color, interior, equipment, and more. Eventually there would be a lot of duplication.

After refactoring, we have a base abstract class with the tests, exposing template methods that derived classes have to implement in order to populate the catalog and also to execute the corresponding action:

[TestFixture] public abstract class
CantChangeConfigurationBecauseThereisImplicitExclusionWhen : ConfigurationTests {
[Test] public void
its_compulsory_has_the_same_family_than_a_configured_equipment() {
CatalogBuilder
.AddEquipmentWithFamily("PR1", "Radio")
.AddEquipmentWithFamily("PR2", "Radio");
AddImplicitExclusionItemWithCompulsories("PR2");
var configuration = Agiven.ModelConfiguration()
.With(Agiven.ConfigEquipment("PR1"))
.Build();
Expect.CallTo(ChooseConflictiveItem(configuration))
.ToThrow<EquipmentsWithSameFamilyException>();
}
protected abstract void AddImplicitExclusionItem(string code);
protected abstract Func<ModelConfiguration> ChooseConflictiveItem(ModelConfiguration configuration);
}
[TestFixture] public class
ChangingColor : CantChangeConfigurationBecauseThereisImplicitExclusionWhen
private const string itemCode = "irrelevant";
protected override void AddImplicitExclusionItem(string code){
CatalogBuilder
.AddColor(itemCode, WithEquipmentsCompulsory(code));
}
protected override Func<ModelConfiguration> ChooseConflictiveItem(ModelConfiguration configuration){
return () => ExecuteChangeColor(itemCode, configuration);
}
}

The base class ConfigurationTests contains just helper methods such as ExecuteChangeColor, or ExecuteChangeInterior, but no tests at all. Otherwise tests would run twice.

Originally published in Carlos Blé's blog.

Volver a posts