Unit Test Mock and State Live Together

Unit Test is an art. There is no silver bullet for what to test, how to test, what tools to use. In my opinion, everything depends on the kind of projects, the maturity of the projects, and the skill of developers in the projects.

The Story

Given this piece of code. It is not a real production code nor production quality code. I made it up to demonstrate in this post.

    public class Account
    {
        public virtual bool IsClosed { get; set; }
        public virtual decimal Amount { get; set; }
        public virtual void Withdraw(decimal? amount)
        {
            Amount -= amount.Value;
        }
    }

    public class AccountManager
    {
        private readonly Account _account;

        public AccountManager(Account account)
        {
            _account = account;
        }

        public void Withdraw(decimal? amount)
        {
            if (_account.IsClosed)
                return;
            if (amount == null)
                throw new ArgumentNullException(nameof(amount));
            _account.Withdraw(amount);
        }
    }

This is not a design or best practice post. Let’s focus on the business logic and what/how to test.

There is an Account with 2 properties:

  1. IsClosed (true/false): If closed, cannot withdraw money.
  2. Amount (decimal): The current total amount left in the bank Account. The amount is reduced by the Withdraw method.

I keep the Account class pretty simple.

To manage an account, we have AccountManager. What it does are

  1. If the account is closed, nothing happens.
  2. If not closed, and the amount is null (to demonstrate the point that it is invalid withdrawn amount), throw ArgumentNullException.
  3. If all is valid, the account’s amount is reduced by the withdrawn amount.

How are we going to test that 3 expected behaviors?

The Analysis

I believe at this phase, we have to depend on the mind of the developer. First, we need to recognize what kind of test we should perform.

#1: Look like an interaction test. We want to verify a certain behavior: Nothing happens.

#2: Verify input valid which expects an exception to be thrown.

#3: Look like a state test. We want to verify the account’s state. Why? Because we do not really care how the money is withdrawn. We only care about the amount left.

The Technology

Let bring in the tools we know to test. At this phase, depend on organizations; size and kind of projects. I work on .NET stack. I will use MS Test + RhinoMock (for interaction test).

#1: When the account is closed, nothing happens.

The account class might have many properties (now or later), but I only care about “IsClosed” property.

    [TestClass]
    public class AccountManagerTest
    {
        [TestMethod]
        public void Withdraw_AccountClosed_DoNothing()
        {
            // Arrange
            var account = MockRepository.GenerateMock<Account>();
            account.Expect(x => x.IsClosed)
                .Return(true);
            account.Expect(x => x.Withdraw(Arg<decimal?>.Is.Anything))
                .Repeat.Never();
            var sut = new AccountManager(account);
            // Act
            sut.Withdraw(null);
            // Assert
            account.VerifyAllExpectations();
        }
    }

The main point is at the “Arrange” phase. I expect these behaviors

  1. The “IsClosed” must be called
  2. The “Withdraw” must not be called. It means: Do nothing.

I am happy to see that test passed.

#2: Throw ArgumentNullException

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Withdraw_AccountNotClosedAndAmountNull_ThrowArgumentException()
        {
            // Arrange
            var account = new Account {IsClosed = false};
            var sut = new AccountManager(account);
            // Act
            sut.Withdraw(null);
            // Assert
        }

I can use either Mock or real object at this test as far as ensuring the IsClosed is false.

#3: If all is valid, account’s amount is reduced by the withdrawn amount

        [TestMethod]
        public void Withdraw_AllValid_AmountReducedByWithdrawnAmount()
        {
            // Arrange
            var account = new Account
            {
                IsClosed = false,
                Amount = 100
            };
            var sut = new AccountManager(account);
            // Act
            sut.Withdraw(30);
            // Assert
            Assert.AreEqual(70, account.Amount);
        }

At this test, I do not care how the system implemented. Given a valid account, and a valid amount to withdraw, my account must have the right amount left.

Be very careful, fellows. If you use mock here, you will end up with interaction test.

 

As I said in the beginning, unit test is an art. You have to research, learn about it every day. Watch other people do it. Make up your own mind. During my career, I found out that developers have a tendency to jump directly to “The Technologies” phase. “The Analysis” phase is very important.

Happy coding and write more unit test.

Write a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.