ActiveObjects + EasyMock + Guice

In the last post we saw how using Guice for dependency injection and EasyMock for mock objects produces a well-designed system that is easy to unit test.  In this post I’ll describe how the ActiveObjects ORM fits in nicely with these other tools.

When using ActiveObjects, each database table is represented by a Java interface, called an entity.  Each column in the database corresponds to get/set methods in the interface.  Individual rows in the table correspond to instances of the interface.  For example, here is a very simple AO entity for a restaurants table:

import net.java.ao.*;

public interface Restaurant extends Entity
{
    public String getName();
    public void setName(String name);

    public String getAddress();
    public void setAddress(String address);

    public String getPhone();
    public void setPhone(String phone);

    public int getTableCount();
    public void setTableCount(int tableCount);
}

Since every entity is an interface, they are incredibly easy to mock.  We saw this in the last example, where the Restaurant object was mocked and then we specified an expectation for its getName() method:

public class ShareTest
{
    private Share share;
    private Twitter twitter;
    private Restaurant restaurant;
    private Credentials credentials;

    @Before
    public void setup()
    {
        twitter = createMock(Twitter.class);
        restaurant = createMock(Restaurant.class);
        credentials = new Credentials("username", "password");
        share = new Share(twitter, credentials);
    }

    @Test
    public void testShare()
    {
        expect(restaurant.getName()).andReturn("Mucho Burrito");
        twitter.update(credentials, "I loved eating at Mucho Burrito!");

        replay(restaurant, twitter);
        share.share(restaurant);
        verify(restaurant, twitter);
    }
}

In a strict definition of unit testing, you should not be accessing a database at all.  The code that you are testing should be completely isolated from everything around it.  By mocking the AO entities in our code, we do not have to deal with databases at all in our unit tests.  (Testing your code with a database is also a good idea though – for that I recommend DbUnit).

AO provides a class called EntityManager that you use to create new entities or query the database for existing entities.  While you can mock an EntityManager using the EasyMock Class Extension in your unit tests, I actually create an IEntityManager interface that duplicates EntityManager’s API, along with a few extra convenience methods.  Any class that needs to create or find entities gets an instance of IEntityManager injected to its constructor (via Guice, of course).  The unit tests for that class then just mock the IEntityManager and pass it into the constructor when creating the instance in the setup() method.

In our restaurant review site, we want to provide a way for users to add new restaurants to the system.  On the back-end we need to take the user’s input (from an HTML form submit) and create a new Restaurant entity.  Let’s assume we use the following class to create a new restaurant:

import com.google.inject.*;

public class CreateRestaurant
{
    private IEntityManager manager;

    @Inject
    public CreateRestaurant(IEntityManager manager)
    {
        this.manager = manager;
    }

    public void create(String name, String address, String phone, int tableCount) throws SQLException
    {
        Restaurant restaurant = manager.create(Restaurant.class);
        restaurant.setName(name);
        restaurant.setAddress(address);
        restaurant.setPhone(phone);
        restaurant.setTableCount(tableCount);
        restaurant.save();
    }
}

Unit testing this class is a simple matter of mocking IEntityManager and Restaurant entities and asserting that proper methods are called on them in the create() method:

import static org.junit.Assert.*;
import static org.easymock.EasyMock.*;
import java.sql.*;
import org.junit.*;

public class CreateRestaurantTest
{
    private IEntityManager manager;
    private Restaurant restaurant;
    private CreateRestaurant creator;

    @Before
    public void setUp() throws Exception
    {
        manager = createMock(IEntityManager.class);
        restaurant = createMock(Restaurant.class);
        creator = new CreateRestaurant(manager);
    }

    @Test
    public void testCreate() throws SQLException
    {
        String name = "Mucho Burrito";
        String address = "50 Mapleview Dr W Barrie, ON L4N 9H5";
        String phone = "(705) 739-6824";
        int tableCount = 15;

        expect(manager.create(Restaurant.class)).andReturn(restaurant);
        restaurant.setName(name);
        restaurant.setAddress(address);
        restaurant.setPhone(phone);
        restaurant.setTableCount(tableCount);
        restaurant.save();

        replay(manager, restaurant);
        assertSame(restaurant, creator.create(name, address, phone, tableCount));
        verify(manager, restaurant);
    }
}

Again, we exploited the fact that the CreateRestaurant class only uses instances of AO interfaces to make unit testing extremely simple & contained.

import static org.junit.Assert.*;
import static org.easymock.EasyMock.*;

import java.sql.*;

import org.junit.*;

import com.pongr.ao.*;

public class CreateRestaurantTest
{
private IEntityManager manager;
private Restaurant restaurant;
private CreateRestaurant creator;

@Before
public void setUp() throws Exception
{
manager = createMock(IEntityManager.class);
restaurant = createMock(Restaurant.class);
creator = new CreateRestaurant(manager);
}

@Test
public void testCreate() throws SQLException
{
String name = “Mucho Burrito”;
String address = “50 Mapleview Dr W Barrie, ON L4N 9H5”;
String phone = “(705) 739-6824”;
int tableCount = 15;
expect(manager.create(Restaurant.class)).andReturn(restaurant);
restaurant.setName(name);
restaurant.setAddress(address);
restaurant.setPhone(phone);
restaurant.setTableCount(tableCount);
restaurant.save();

replay(manager, restaurant);
assertSame(restaurant, creator.create(name, address, phone, tableCount));
verify(manager, restaurant);
}
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s