How to Test a Class Using an Implementation Helper ?

Suppose you have some duplicated code in the Foo & Bar classes. You managed to extracted this code in an helper class. Fine, the helper class can now be tested on its own, but how do I get rid of duplication in FooTest and BarTest ?

I though of injecting a mock on the helper class in Foo & Bar when testing, to make sure the helper instance is correctly used, but sometimes, it just feel as if testing an implementation …

Here is what we eventually did at work : create an abstract base class to test the api of the helper class. Implement this abstract class once to test the helper class itself, and then implement it once each time it is reused.

Here is an exemple with a ListBuilder helper class that wraps a list and returns a copy of it each time it is asked for.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ListBuilder<T>
{
  private readonly List<T> internalList = new List<T>();
  private List<T> listSnapshot = new List<T>();
  private readonly object locker = new object();

  public List<T> ListSnapshot()
  {
    lock (locker)
    {
      if (listSnapshot.Count != internalList.Count)
      {
        listSnapshot = new List<T>(internalList);
      }
    }

    return listSnapshot;
  }

  public void Add(T newItem)
  {
    lock (locker)
    {
      internalList.Add(newItem);
    }
  }
}

Here is the tests for ListBuilder class itself

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public abstract class ListBuilderContractTest<T>
{
  protected abstract void AddAnItem();
  protected abstract void VerifyAddedItem(T addedItem);
  protected abstract List<T> ListCopy();

  [Test]
  public void TheDefaultListShouldBeEmpty()
  {
    Assert.AreEqual(0, ListCopy().Count);
  }

  [Test]
  public void AddShouldAddItemInList()
  {
    AddAnItem();
    VerifyItemWasAdded();
  }

  protected void VerifyItemWasAdded()
  {
    var copy = ListCopy();
    Assert.AreEqual(1, copy.Count);
    VerifyAddedItem(copy[0]);
  }

  [Test]
  public void WhenOfSameSizeNewListCopyShoulReturnSameList()
  {
    AddAnItem();
    Assert.AreSame(ListCopy(), ListCopy());
  }

  [Test]
  public void CopiesShouldBeSnapshots()
  {
    var copy = ListCopy();

    AssertExecution.Of(AddAnItem)
      .ShouldNotChange(() => copy.Count);
  }
}

[TestFixture]
public class ListBuilderTest : ListBuilderContractTest<string>
{
  private const string ADDED_ITEM = "toto";

  private ListBuilder<string> appender;

  [SetUp]
  public void SetUp()
  {
    appender = new ListBuilder<string>();
  }

  protected override void AddAnItem()
  {
    appender.Add(ADDED_ITEM);
  }
  protected override void VerifyAddedItem(string addedItem)
  {
    Assert.AreEqual(ADDED_ITEM, addedItem);
  }

  protected override List<string> ListCopy()
  {
    return appender.ListSnapshot();
  }
}

And here are tests for other class using it. First a simple one :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[TestFixture]
public class OrderPresenterAsExecListBuilderTest : ListBuilderContractTest<IExecRowPresenter>
{
  private OrderPresenter presenter;
  private Mock<IExec> exec;

  [SetUp]
  public void SetUp()
  {
    exec = new Mock<IExec>();
    exec.Setup(x => x.Id).Returns("an exec");
    exec.Setup(x => x.Price).Returns(123.456);
    exec.Setup(x => x.Quantity).Returns(36);

    presenter = new OrderPresenter();
  }

  protected override void AddAnItem()
  {
    presenter.AddExec(exec.Object);
  }

  protected override void VerifyAddedItem(IExecRowPresenter addedItem)
  {
    Assert.AreEqual(exec.Object.Id, addedItem.Id);
    Assert.AreEqual(exec.Object.Price, addedItem.Price);
    Assert.AreEqual(exec.Object.Quantity, addedItem.Quantity);
  }

  protected override List<IExecRowPresenter> ListSnapshot()
  {
    return presenter.GraphicalExecs;
  }
}

Then a more complex one :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[TestFixture]
public class StrategyPresenterAsOrderPresenterListBuilderTest : ListBuilderContractTest<IOrderPresenter>
{
  private StrategyPresenter presenter;
  private Mock<IStrategy> strategy;
  private Mock<IOrder> order;

  [SetUp]
  public void SetUp()
  {
    strategy= new Mock<IStrategy> { DefaultValue = DefaultValue.Mock };

    order = new Mock<IOrder>();
    order.Setup(x => x.Id).Returns(123);

    presenter = new StrategyPresenter(strategy.Object);
  }

  protected override void AddAnItem()
  {
    strategy.Raise(x => x.SentQuantityChanged += null, new SentQuantityChangedEventArgs {Origin = order.Object});
  }

  protected override void VerifyAddedItem(IOrderPresenter addedItem)
  {
    Assert.AreEqual(order.Object.Id, addedItem.Id);
  }

  protected override List<IOrderPresenter> ListSnapshot()
  {
    return presenter.GraphicalOrderList;
  }


  [Test]
  public void ExecutedQuantityChangedShouldAddAnItem()
  {
    strategy.Raise(x => x.ExecutedQuantityChanged += null, new ExecutedQuantityChangedEventArgs {Origin = order.Object});

    VerifyItemWasAdded();
  }
}

The solution really pleases me :

    No code duplication
    Robust tests
    No artificial mocking

Comments