Testing Databases with JUnit and Hibernate Part 2: The Mother of All Things

In the first part of this little series I showed a simple test based on a simple data model. As we saw the code for the test wasn’t simple at all, but quite ugly. We started to fix that by moving out all the SessionFactory and Session handling stuff out into a Rule. Leaving us with test code like this:

  1. public class SuperHeroRepository3Test {
  2.  
  3.  @Rule
  4.  public final SessionFactoryRule sf = new SessionFactoryRule();
  5.  
  6.  @Test
  7.  public void returnsHerosWithMatchingType() {
  8.   Session session = sf.getSession();
  9.  
  10.   SuperPowerType powerType = createSuperPowerType();
  11.   session.save(powerType);
  12.  
  13.   SuperPower superpower = createSuperPower(powerType);
  14.   session.save(superpower);
  15.  
  16.   SuperHero hero = createSuperHero(superpower);
  17.   session.save(hero);
  18.  
  19.   sf.commit();
  20.  
  21.   SuperHeroRepository heroRepository = new SuperHeroRepository(session);
  22.   List<SuperHero> heroes = heroRepository.loadBy(superpower);
  23.  
  24.   assertNotNull(heroes);
  25.   assertEquals(1, heroes.size());
  26.  }
  27.  
  28.  private SuperHero createSuperHero(SuperPower superpower) {
  29.   SuperHero hero = new SuperHero();
  30.   hero.name = "Name";
  31.   hero.power = superpower;
  32.   hero.weakness = "None";
  33.   hero.secretIdentity = "Mr. Jones";
  34.   return hero;
  35.  }
  36.  
  37.  private SuperPower createSuperPower(SuperPowerType powerType) {
  38.   SuperPower superpower = new SuperPower();
  39.   superpower.name = "SuperPower";
  40.   superpower.description = "Description";
  41.   superpower.type = powerType;
  42.   return superpower;
  43.  }
  44.  
  45.  private SuperPowerType createSuperPowerType() {
  46.   SuperPowerType powerType = new SuperPowerType();
  47.   powerType.name = "TheType";
  48.   powerType.description = "12345678901234567890aDescription";
  49.   return powerType;
  50.  }
  51. }

Since we won’t be satisfied with a single test we’ll wan’t to reuse the createXXX methods. The starting idea for how to do this comes from Martin Fowler. He describes ObjectMothers as objects wich give birth to complex objects needed for testing. This is exactly the information we are in. So the createXXX Methods should go to ObjectMothers. As you can see in the current version of the test, there are two things an ObjectMother must do:

  • create Instances of a class that have all attributes filled in a way that satisfies the constraints present in the database.
  • store them in the hibernate session.

So here is a very simple version of such an ObjectMother:

  1. public class SuperPowerTypeMother {
  2.  
  3.  private final Session session;
  4.  
  5.  public SuperPowerTypeMother(Session s) {
  6.   session = s;
  7.  }
  8.  
  9.  public SuperPowerType instance() {
  10.   SuperPowerType powerType = new SuperPowerType();
  11.   powerType.name = "TheType";
  12.   powerType.description = "12345678901234567890aDescription";
  13.   session.save(powerType);
  14.   return powerType;
  15.  }
  16. }

This gets used by the ObjectMother for SuperPowers:

  1. public class SuperPowerMother {
  2.  
  3.  private final Session session;
  4.  
  5.  public SuperPowerMother(Session s) {
  6.   session = s;
  7.  }
  8.  
  9.  public SuperPower instance() {
  10.   SuperPower superpower = new SuperPower();
  11.   superpower.name = "SuperPower";
  12.   superpower.description = "Description";
  13.   superpower.type = new SuperPowerTypeMother(session).instance();
  14.   session.save(superpower);
  15.   return superpower;
  16.  }
  17.  
  18. }

Finally add a similar SuperHeroMother and our test looks like this:

  1. public class SuperHeroRepository4Test {
  2.  
  3.  @Rule
  4.  public final SessionFactoryRule sf = new SessionFactoryRule();
  5.  
  6.  @Test
  7.  public void returnsHerosWithMatchingType() {
  8.   SuperHero hero = new SuperHeroMother(sf.getSession()).instance();
  9.  
  10.   sf.commit();
  11.  
  12.   SuperHeroRepository heroRepository = new SuperHeroRepository(
  13.     sf.getSession());
  14.   List<SuperHero> heroes = heroRepository.loadBy(hero.power);
  15.  
  16.   assertNotNull(heroes);
  17.   assertEquals(1, heroes.size());
  18.  }
  19. }

This is nice. Short and concentrated on what we want to test. Only the session hints at the presence of the database. Also when our entities get new attributes we have a single place to set a default value for that attribute for all our tests. All thats left in the test for creating a SuperHero is a one liner like this:

  1. SuperHero hero = new SuperHeroMother(sf.getSession()).instance();

So are we done? No, because the ObjectMothers have two serious problems:

  • they create new entities every time, which will result in unique constraint violations once you deal with more then one SuperHero
  • they set the attributes in the same way every time, but sooner or later you want a hero with a special SuperPower, so we want to specify some attributes we are interested in without worrying about the others.

In order to solve the first problem mothers must look in the database if an instance as the one requested is already present. For this the alternate key of the entity is used, and only if a select with this alternate key doesn’t return an entity a new one is created. Note that since each entity gets stored in the session before it leaves the mother each such lookup select causes a flush of the session and therefore sees previously created entities.

In order to set attributes of the entities a builder pattern is used. The mothers have a method named after the attribute, taking an attribute value, and returning the mother instance.

So the SuperHeroObjectMother now looks like this.

  1. public class SuperHeroMother {
  2.  private final Session session;
  3.  
  4.  private String secretIdentity = "Mr. Jones";
  5.  private String name = "Name";
  6.  private String weakness = "None";
  7.  private SuperPower power = null;
  8.  
  9.  public SuperHeroMother(Session s) {
  10.   session = s;
  11.   power = new SuperPowerMother(session).instance();
  12.  }
  13.  
  14.  public SuperHero instance() {
  15.   SuperHero hero = loadInstance();
  16.   if (hero == null) {
  17.    hero = createInstance();
  18.   }
  19.   hero.power = power;
  20.   hero.weakness = weakness;
  21.   hero.secretIdentity = secretIdentity;
  22.   session.save(hero);
  23.   return hero;
  24.  }
  25.  
  26.  private SuperHero loadInstance() {
  27.   return (SuperHero) session.createCriteria(SuperHero.class)
  28.     .add(Restrictions.eq("name", name)).uniqueResult();
  29.  
  30.  }
  31.  
  32.  private SuperHero createInstance() {
  33.   SuperHero hero = new SuperHero();
  34.   hero.name = name;
  35.   return hero;
  36.  }
  37.  
  38.  public SuperHeroMother name(String aName) {
  39.   name = aName;
  40.   return this;
  41.  }
  42.  
  43.  public SuperHeroMother secretIdentity(String aSecretIdentity) {
  44.   secretIdentity = aSecretIdentity;
  45.   return this;
  46.  }
  47.  
  48.  public SuperHeroMother power(SuperPower aPower) {
  49.   power = aPower;
  50.   return this;
  51.  }
  52.  
  53.  public SuperHeroMother weaknes(String aWeakness) {
  54.   weakness = aWeakness;
  55.   return this;
  56.  }
  57. }

Using such an object mother is extremely easy: create a mother instance, set the attributes you are interested in and call instance().

But writing a Mother is a little tedious and if done as shown above involves a lot of code duplication. So in the next part of the series we’ll do some refactoring in order to remove at least some of the code duplication.


Share:
  • DZone
  • Digg
  • del.icio.us
  • Reddit
  • Facebook
  • Twitter