views:

429

answers:

3

My first programming job introduced me to unit testing and the concept of mock objects, but something always felt wrong about it.

Let's say we were writing a bank app, and needed to mock a BankAccount object:


    // boilerplate code
    public interface IBankAccount {
        void Deposit(int amount);
        void Withdrawal(int amount);
        int getBalance();
        int getAccountNumber();
    }

    public interface IBankAccountFactory {
        IBankAccount getAccount(int accountNumber);
    }

    public class ProductionBankAccountFactory implements IBankAccountFactory { 
        public IBankAccount getAccount(int accountNumber) {
            return new RealBankAccount(accountNumber);
        }
    }

    public class MockBankAccountFactory implements IBankAccountFactory {
        public IBankAccount getAccount(int accountNumber) {
            return new MockBankAccount(accountNumber);
        }
    }

    public static class BankAccountFactory {
        // ewww, singletons!
        public static IBankAccountFactory Instance;
    }

    // finally, my actual business objects
    public class MockBankAccount implements IBankAccount {
        public MockBankAccount(int accountNumber) { ... }
        // interface implementation
    }

    public class RealBankAccount implements IBankAccount {
        public RealBankAccount(int accountNumber) { ... }
        // interface implementation
    }

Each class has a purpose:

  • The factory and factory interfaces exist to wrap up the constructors to our mock and real objects.
  • The static BankAccountFactory class allows us to assign BankAccountFactory.Instance an instance of IRealBankAccountFactory or MockBankAccountFactory at the start of our production app or tests respectively.
  • Once everything is set up properly, any class can grab an instance of IBankAccount simply by calling:

    BankAccountFactory.Instance.getAccount(accountNum);

This works, but it results in a lot of boilerplate code. I shouldn't have to write 5 new classes for each class I want to mock. I'm convinced there's an easier way, so I have to ask the SO community:

Is there a better or preferred way of writing mock objects?

[Edit to add:] I appreciate the links to the mocking and DI frameworks, but right now I'm working on a 500 KLOC application, and at least 60% of the code consists of the boilerplate mock classes in the style above.

I just want to reduce the size of the codebase without re-writing large chunks of code for Yet-Another-Framework™, so it helps me more to see mock classes written by hand. :)

+8  A: 

The better way is to have someone else write it. Some options here are:

Moq - http://code.google.com/p/moq/

Rhino Mocks - http://ayende.com/projects/rhino-mocks.aspx

casperOne
A: 

There are Mock libraries that simplify the process by allowing you to specify the object and it behaviour in the unit testing code.

A good example is the Moq library (http://code.google.com/p/moq/)

Megacan
+1  A: 

I guess my first question is why you need to use a factory pattern to wrap the construction of your objects; in particular your Mock object. Since each unit test in a suite should run completely independently of any other unit tests, it seems like you would be able to instantiate your MockBankAccount directly in the setUp method of your unit test class, or even in the test itself. If I were in the situation above, I would write something like this:

public interface IBankAccount {
    void Deposit(int amount);
    void Withdrawal(int amount);
    int getBalance();
    int getAccountNumber();
}

public class MockBankAccountFactory implements IBankAccountFactory {
    public IBankAccount getAccount(int accountNumber) {
        return new MockBankAccount(accountNumber);
    }
}

public class BankAccountUnitTest extends TestCase {
    IBankAccount testBankAccount;

    public void setUp() {
        testBankAccount = new MockBankAccount(someAccountNumber);
    }

    // Unit tests here
}

If you're using the factory in order to unit test another class that uses IBankObject, then you should look into dependency injection to supply a mock object to that class, rather than having the class under test instantiate a mock object.

MattK