How to Write Unit Tests for Temporal Coupling between Method Calls

by Zoran Horvat

Temporal coupling occurs when one method call must be made before another method is called. Such situation is generally considered a trait of a bad design. We are often trying to avoid any concrete kind of coupling, including coupling between method calls. Anyway, it is not always possible to avoid it.

Temporal Coupling in Disposable Types

Typical scenario in which method calls are coupled occurs in disposable types. Consider an object which we are using to perform some operation and the object is, by pure chance, disposable. For example, we could imagine an implementation of a Unit of Work, which exposes a Commit method. Commit() must be called before Unit of Work is disposed. Otherwise, premature disposal would cause all unsaved changes to be discarded. Here is the client code which operates on the unit of work:

using (UnitOfWork uow = uowFactory.Create())
{
    // make changes to data
    uow.Commit();
}

When the using instruction is unfolded, we can see that the Dispose() method is invoked just after the Commit() method:

UnitOfWork uow = uowFactory.Create();
// make changes to data
uow.Commit();
uow.Dispose();

The last two calls on the uow object are exhibiting temporal coupling.

Temporal Coupling in an Isolated Use Case

There are more situations in which method calls are coupled, but in certain execution scenarios only. Take a look at the following piece of code. It is part of a larger operation which manages a money account:

account.Deposit(10.0);
decimal balance = account.GetBalance();
RaiseNewBalanceEvent(balance);

In this piece of code, account object exposes Deposit() and GetBalance() methods. Each of these methods can be called alone, without calling the other one. Therefore, we don't have proper temporal coupling between these two methods. Temporal coupling occurs only in this specific use case.

In this particular operation, we want to be sure that the new balance event is raised with the new balance as its argument. In other words, we want to make sure that the balance variable has been populated strictly after a call has been made to the Deposit() method and never before call to the Deposit(). Otherwise, new balance event would be raised with the old balance, which would be wrong.

This situation shows that in one particular use case these two methods are coupled. In some other use cases, they might not be coupled.

The Problem of Testing Coupled Methods

Now that we have seen two examples of temporal coupling between method calls, we can think of the ways in which we could test that effect. Namely, we want to write a set of unit test which can prove that two methods have been invoked, and that they were invoked in the correct relative order.

Solution that I plan to provide in this article will be based on providing specific test doubles. Normally, the role of the test double is to be sensitive to certain effects we are interested in. Right now, we are interested in testing the coupling between method calls, and therefore we will need test doubles that are sensitive to the relative order of method invocations.

As the first approach, we will provide a manual test double. That means to manually write a special-purpose class which will be served as the test double. In the second attempt, we will rely on the mocking framework, Moq to be precise, which will generate a dynamic test double with the same purpose.

For the sake of completeness, let me outline the class we are testing:

class AccountManager
{
    public void Deposit(IAccount account, decimal amount)
    {
        account.Deposit(amount);
        decimal balance = account.GetBalance();
        this.RaiseNewBalanceEvent(balance);
    }

    private void RaiseNewBalanceEvent(decimal newBalance)
    {
        ...
    }
}

And here is the interface which defines the account:

interface IAccount
{
    void Deposit(decimal amount);
    decimal GetBalance();
}

Now the task is to write a unit test which proves that method GetBalance() has been invoked on the account object strictly after the call was made to the Deposit() method on the same account object.

Using the Manual Test Double

The first technique I plan to show you will be based on a manually coded test double for the IAccount implementation. The role of this replacement class will be to allow both Deposit() and GetBalance() methods to be invoked, of course, but also to put additional constraints on the caller. Namely, if GetBalance() method has been invoked with no preceding call to the Deposit() method, we want this test double to cause the enclosing unit test to fail.

The code is telling more than words, so here is the entire implementation of the replacement account class:

class CouplingAccountDouble: IAccount
{
    private Action OnGetBalance { get; set; }= () => Assert.Fail();

    public void Deposit(decimal amount)
    {
        this.OnGetBalance = () => { };
    }

    public decimal GetBalance()
    {
        this.OnGetBalance();
        return 0;
    }
}

This short class is all we need to ensure that Deposit() method has been invoked before the GetBalance() method. GetBalance() method is invoking the OnGetBalance action, which is initialized such that it causes test to fail unconditionally. But when the Deposit() method is invoked, this action is set to an empty action, causing no failure when invoked.

Therefore, if the caller makes correct order of steps, calling Deposit() before GetBalance(), everything will be fine when this action is triggered, because its body will be empty. But if the caller switches the order of operations, calling GetBalance() before Deposit(), or even forgetting to call Deposit() entirely, this OnGetBalance action will remain in its initial form and it will cause the test to fail.

Apart from this test, we also have to make sure that Deposit() method is invoked exactly once. Here's the deal about that. Deposit() method is changing the balance in the way which is not idempotent – the operation must not be repeated. Therefore, we want to be sure that balance has been changed exactly once. And also we want to make sure that GetBalance() has not been invoked before that, or otherwise we would work with the old balance. We can use another test double for the purpose:

class DepositCountingAccount: IAccount
{
    public int Count { get; private set; }

    public void Deposit(decimal amount)
    {
        this.Count += 1;
    }

    public decimal GetBalance() => 0;
}

This test double is even simpler than the first one. Now we can write unit tests for the account manager class.

[TestClass]
public class AccountManagerTests
{
    [TestMethod]
    public void Deposit_ReceivesPositiveAmount_InvokesDepositOnAccountOnce()
    {
        AccountManager mgr = new AccountManager();
        DepositCountingAccount account = new DepositCountingAccount();

        mgr.Deposit(account, 10M);

        Assert.AreEqual(1, account.Count);

    }

    [TestMethod]
    public void Deposit_ReceivesPositiveAmount_DepositInvokedBeforeGetBalance()
    {
        AccountManager mgr = new AccountManager();
        IAccount account = new CouplingAccountDouble();

        mgr.Deposit(account, 10M);
    }
}

The first unit test is using the counting account test double. It asserts that number of calls made to the Deposit() method on the account object is strictly equal to one. The test will fail if Deposit() was not called at all, or was called more than once.

The second test is equally simple. This time, it relies on the coupling test double. Note that this test doesn't have any assertions. Its entire purpose is to survive the call to the Deposit() method. Test double will do the rest - if temporally coupled methods of the account object have been invoked in the incorrect relative order, the account object itself will cause the test to fail. If that didn't happen, and if test execution passed beyond call to the Deposit() method, then everything is fine and test assumption has already been proved. Therefore, the test will succeed.

This is entire implementation of both the tests and mock objects. However simple, it took two new classes to be added to the design. In the remainder of this article, we will rely on the mocking framework to do the heavy lifting for us.

Using the Automatic Test Double

When test doubles are simple as shown above, then it might be a good idea to use a mocking framework than to write manual mocks. Despite somewhat awkward syntax that comes with automatic mocks, they are helping write effective unit tests on more than one account.

Probably the most appealing one is that entire test setup is located in the test method itself. With manual mocks, vital part of the test setup is located in a separate class. And that is what makes it harder to read the test method. Let's see what it would look like if both of the unit tests given above are rewritten to use the Moq framework. Most of the other popular mocking frameworks would yield similar results, so it is not really important which one of them is used.

Here are the test methods:

[TestClass]
public class AccountManagerTests
{
    [TestMethod]
    public void Deposit_ReceivesPositiveAmount_InvokesDepositOnAccountOnce()
    {
        AccountManager mgr = new AccountManager();

        Mock<IAccount> accountMock = new Mock<IAccount>();
        accountMock.Setup(acc => acc.Deposit(It.IsAny<decimal>())).Verifiable();

        mgr.Deposit(accountMock.Object, 10M);

        accountMock.Verify(acc => acc.Deposit(It.IsAny<decimal>()), Times.Once);

    }

    [TestMethod]
    public void Deposit_ReceivesPositiveAmount_DepositInvokedBeforeGetBalance()
    {
        AccountManager mgr = new AccountManager();

        Mock<IAccount> accountMock = new Mock<IAccount>();
        accountMock.Setup(acc => acc.GetBalance()).Callback(
            () => Assert.Fail()).Returns(0);
        accountMock.Setup(acc => acc.Deposit(It.IsAny<decimal>())).Callback(
            () =>
            {
                accountMock.Setup(a => a.GetBalance()).Returns(0);
            });

        mgr.Deposit(accountMock.Object, 10M);
    }
}

Although code is quite descriptive, it might be helpful to give some explanations. The first test is declaring that the Deposit() method is verifiable, which, in the tongue of the Moq framework, means that we can make assertions against calls made to that method. And that is precisely what we are doing at the very end of the test method - we are asserting that Deposit() method has been invoked exactly once.

The second test method is more interesting, though. It contains two steps when setting up the account mock. The first step says that call to the GetBalance() method should fail unconditionally. In terms of the Moq framework, we are providing the callback function to execute whenever the GetBalance() method is invoked - and this action simply fails. But then the second setup step comes, and that is the setup for the Deposit() method. When this method is invoked, it actually changes the setup for the GetBalance() method.

That is an interesting idea - the new setup for the GetBalance() method simply says that the GetBalance() method should return value zero. No trace of failure anymore. In other words, after the Deposit method is invoked, the caller will be perfectly safe to make calls to the GetBalance() method, as many times as it desires, and nothing will fail beyond that point.

This is how we can leverage the mocking framework to write both tests - the one counting critical calls, and the other one ensuring that temporal coupling has been exercised as expected. When done right, test setup will be simple and it will be focused to only constraining behavior in that part which we have interest in: temporal coupling between method calls.

Summary

In this article we have analyzed one situation which occurs frequently in practice - coupling between method calls in which one method must be invoked before the other one.

When it comes to writing unit tests that are proving that methods are invoked in the right order, we have seen one tactic which is based on changing the test setup as target methods are being invoked. Before the first method is invoked, the test is set up in such way that it fails on the second method invocation. But as soon as the first method has indeed been invoked, the test setup is immediately changed in such way that failure is not triggered.

By performing this process, we are making sure that the test will fail if the second method has been invoked before the first method. As it normally goes with unit tests, they are ruling out negative cases. And so it was in the example presented in this article. Unit test for temporal coupling between method calls is ruling out the negative case in which calls are placed in the opposite order. This test does not prove that methods were even invoked, nor does it count how many times any of the methods was called. Those aspects are the target of other unit tests.


If you wish to learn more, please watch my latest video courses

About

Zoran Horvat

Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.

  1. Pluralsight
  2. Udemy
  3. Twitter
  4. YouTube
  5. LinkedIn
  6. GitHub