Specs2 Spring 0.3

Spring 3.1 brings bean profiles, allowing you to name sets of beans that will be included in your ApplicationContext according to your specifications. For example, I could have bean profiles named UCI and ACU. In both profiles, I will have a bean that implements the LegalRegulations interface, but the implementations will be different. At runtime, I will specify which bean profiles I want to use and Spring will pick the appropriate beans for the given profile. Let me show you some code:

public interface LegalRegulations {
  boolean hasDoped(Rider rider);	
}

@Component
@Profile("UCI")
public class UCILegalRegulations implements LegalRegulations {
  @Override
  public boolean hasDoped(Rider rider) {
    return true;
  }
}

@Component
@Profile("ACU")
public class ACULegalRegulations implements LegalRegulations {

  @Override
  public boolean hasDoped(Rider rider) {
    return false;
  }
}

Now, I have two beans that implement the same interface. Depending on which country we run the application in, we want to use the appropriate implementation of the LegalRegulations interface. Notice in the code above the @Profile annotation with a constant that specifies the name of the profile in which the bean should be included.

In addition to the profiled beans, I have other beans that are included in every profile. The last bean that I will show you is the SomeComponent bean (which just so happens to be implemented in Scala).

@Component
class SomeComponent 
  @Autowired()(private val hibernateTemplate: HibernateTemplate) {

  def findAll(entityType: Class[_]) =
    this.hibernateTemplate.loadAll(entityType)

  def generate(count: Int) {
    for (c <- 0 until count) {
      val rider = new Rider()
      rider.setName("Rider #" + c)
      rider.setUsername("user " + c)
      this.hibernateTemplate.saveOrUpdate(rider)
    }
  }

  def getByUsername(username: String) = {
    val riders = this.hibernateTemplate.findByCriteria(
      DetachedCriteria.forClass(classOf[Rider]).add(
        Restrictions.eq("username", username)))
    riders.get(0).asInstanceOf[Rider]
  }

}

This bean depends on the HibernateTemplate, which in turn depends on SessionFactory; the SessionFactory depends on a DataSource, which is looked up in JNDI. The Spring context file for the application is simply

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ... >

  <context:component-scan base-package="org.specs2.springexample"/>

  <tx:jta-transaction-manager />

  <tx:annotation-driven />

  <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/test" 
    expected-type="javax.sql.DataSource"/>

  <bean id="sessionFactory" 
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="packagesToScan">
      <list>
        <value>org.specs2.springexample</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.show_sql=true
        hibernate.dialect=org.hibernate.dialect.HSQLDialect
        hibernate.query.factory_class=org.hibernate.hql.ast.ASTQueryTranslatorFactory
        hibernate.cache.use_structured_entries=true
        hibernate.hbm2ddl.auto=create-drop
      </value>
    </property>
  </bean>

  <bean id="hibernateTemplate" 
    class="org.springframework.orm.hibernate3.HibernateTemplate">
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>

</beans>

Testing it

Now, to test the application, I need to prepare the JNDI environment and specify the profile that I want to use for testing. Nothing is easier in Specs2 Spring. All I need to do is to create the class that contains the specifications; that extends org.specs2.spring.Specification and is annotated with the Specs2 Spring annotations. (There is much more detail at http://www.cakesolutions.org/specs2-spring.html.) In this post, I will show just the code that is necessary to test the trivial rider manager application.

@Transactional
@TransactionConfiguration (defaultRollback = true)
@ContextConfiguration(
  Array("classpath*:/META-INF/spring/module-context.xml"))
@UseProfile(Array("ACU"))
@SystemEnvironment(Array("efoo=bar;ebaz=null"))
@SystemProperties(Array("pfoo=bar;pbaz=null"))
@DataSource(name = "java:comp/env/jdbc/test",
  driverClass = classOf[JDBCDriver], url = "jdbc:hsqldb:mem:test")
@TransactionManager(name = "java:comp/TransactionManager")
class ApplicationSpec extends Specification 
  with HibernateDataAccess with BeanTables {

  @Autowired var regulations: LegalRegulations = _
  @Autowired var someComponent: SomeComponent = _
  
  "no-one dopes!" in {
    "age" | "username" | "name"    | "teamName" |
       32 ! "wheeler"  ! "Wheeler" ! "Wheelers" |
       30 ! "nemesis"  ! "Nemesis" ! "Baddies"  |> insert[Rider]

    val rider = this.someComponent.getByUsername("wheeler")
    regulations.hasDoped(rider) must be_== (false)
  }
  
}

Specs2 Spring understands all the annotations and prepares the JNDI environment (the DataSource and the TransactionManager), then creates the Spring ApplicationContext using the configuration files specified in @ContextConfiguration annotation with the ACU profile (see the @UseProfile(Array("ACU")) annotation). It also specifies the system environment and system properties to be efoo => "bar" and ebaz => null and pfoo => "bar" and pbaz => null. Presumably, some beans use the @Value annotation with the SPEL expression that extracts the value from the environment.

Getting it

If you want to get your hands on Specs2 Spring 0.3, go to https://github.com/janm399/specs2-spring. If you find a bug or if you’d like a feature to be included, please do create an issue at https://github.com/janm399/specs2-spring/issues.

This entry was posted in Jan's Blog and tagged , , . Bookmark the permalink.

Leave a Reply